目标是创建一个行为类似db结果集的模拟类。
例如,如果一个数据库查询返回,使用dict表达式,{'ab':100, 'cd':200},那么我想看到:
>>> dummy.ab
100
一开始我想也许我可以这样做:
ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
def __init__(self, ks, vs):
for i, k in enumerate(ks):
self[k] = vs[i]
setattr(self, k, property(lambda x: vs[i], self.fn_readyonly))
def fn_readonly(self, v)
raise "It is ready only"
if __name__ == "__main__":
c = C(ks, vs)
print c.ab
但是cab返回一个属性对象。
用k = property(lambda x: vs[i])替换setattr行根本没有用。
那么,在运行时创建实例属性的正确方法是什么呢?
附注:我知道在如何使用__getattribute__方法中提出了一个替代方案?
对于那些来自搜索引擎的人,这里是我在谈论动态属性时寻找的两件事:
class Foo:
def __init__(self):
# we can dynamically have access to the properties dict using __dict__
self.__dict__['foo'] = 'bar'
assert Foo().foo == 'bar'
# or we can use __getattr__ and __setattr__ to execute code on set/get
class Bar:
def __init__(self):
self._data = {}
def __getattr__(self, key):
return self._data[key]
def __setattr__(self, key, value):
self._data[key] = value
bar = Bar()
bar.foo = 'bar'
assert bar.foo == 'bar'
如果你想放置动态创建的属性,__dict__是很好的。__getattr__只适合在需要该值时执行某些操作,例如查询数据库。set/get组合有助于简化对存储在类中的数据的访问(如上面的示例)。
如果您只想要一个动态属性,请查看property()内置函数。
如何动态地将属性添加到python类?
假设您有一个希望向其添加属性的对象。通常,当我需要开始管理对具有下游用途的代码中的属性的访问时,我希望使用属性,以便能够维护一致的API。现在,我通常会将它们添加到定义对象的源代码中,但让我们假设您没有这种访问权限,或者您需要真正地以编程方式动态地选择函数。
创建类
使用一个基于属性文档的例子,让我们创建一个具有“hidden”属性的对象类,并创建它的一个实例:
class C(object):
'''basic class'''
_x = None
o = C()
在Python中,我们期望有一种明显的做事方式。但是,在本例中,我将展示两种方法:使用装饰符符号和不使用装饰符符号。首先,没有装饰符号。对于getter、setter或delete的动态赋值,这可能更有用。
动态(又名猴子修补)
让我们为我们的类创建一些:
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
现在我们把这些赋值给性质。注意,我们可以在这里以编程方式选择函数,回答动态问题:
C.x = property(getx, setx, delx, "I'm the 'x' property.")
和用法:
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
>>> help(C.x)
Help on property:
I'm the 'x' property.
修饰符
我们可以用上面的装饰符符号做同样的事情,但在这种情况下,我们必须将所有方法命名为相同的名称(我建议与属性保持相同),因此编程赋值并不像使用上述方法那样简单:
@property
def x(self):
'''I'm the 'x' property.'''
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
并将属性对象及其配置的setter和deleters赋值给类:
C.x = x
和用法:
>>> help(C.x)
Help on property:
I'm the 'x' property.
>>> o.x
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
我在Stack Overflow的这篇文章中问了一个类似的问题,以创建一个创建简单类型的类工厂。结果就是这个答案,它有一个类工厂的工作版本。
以下是答案的一小段:
def Struct(*args, **kwargs):
def init(self, *iargs, **ikwargs):
for k,v in kwargs.items():
setattr(self, k, v)
for i in range(len(iargs)):
setattr(self, args[i], iargs[i])
for k,v in ikwargs.items():
setattr(self, k, v)
name = kwargs.pop("name", "MyStruct")
kwargs.update(dict((k, None) for k in args))
return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})
>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>>
你可以使用这个的一些变化来创建默认值,这是你的目标(在这个问题中也有一个答案)。
这似乎是可行的(但见下文):
class data(dict,object):
def __init__(self,*args,**argd):
dict.__init__(self,*args,**argd)
self.__dict__.update(self)
def __setattr__(self,name,value):
raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
def __delattr__(self,name):
raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
如果您需要更复杂的行为,请随意编辑您的答案。
edit
对于大型数据集,以下方法可能更节省内存:
class data(dict,object):
def __init__(self,*args,**argd):
dict.__init__(self,*args,**argd)
def __getattr__(self,name):
return self[name]
def __setattr__(self,name,value):
raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
def __delattr__(self,name):
raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
如果需求是基于某些实例属性动态生成属性,那么下面的代码可能会有用:
import random
class Foo:
def __init__(self, prop_names: List[str], should_property_be_zero: bool = False) -> None:
self.prop_names = prop_names
self.should_property_be_zero = should_property_be_zero
def create_properties(self):
for name in self.prop_names:
setattr(self.__class__, name, property(fget=lambda self: 0 if self.should_property_be_zero else random.randint(1, 100)))
需要注意的重要一点是使用setattr(self。__class__进行名称,…),而不是setattr(自我、名称…)
使用的例子:
In [261]: prop_names = ['a', 'b']
In [262]: ff = Foo(prop_names=prop_names, should_property_be_zero=False)
In [263]: ff.create_properties()
In [264]: ff.a
Out[264]: 10
In [265]: ff.b
Out[265]: 37
In [266]: ft = Foo(prop_names=prop_names, should_property_be_zero=True)
In [267]: ft.create_properties()
In [268]: ft.a
Out[268]: 0
In [269]: ft.b
Out[269]: 0
设置属性将引发AttributeError:不能按预期设置属性:
In [270]: ff.a = 5
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-270-5f9cad5b617d> in <module>
----> 1 ff.a = 5
AttributeError: can't set attribute
In [271]: ft.a = 5
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-271-65e7b8e25b67> in <module>
----> 1 ft.a = 5
AttributeError: can't set attribute