目标是创建一个行为类似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__方法中提出了一个替代方案?
虽然给出了很多答案,但我没有找到一个让我满意的。我找到了自己的解决方案,使属性适用于动态情况。来源回答原来的问题:
#!/usr/local/bin/python3
INITS = { 'ab': 100, 'cd': 200 }
class DP(dict):
def __init__(self):
super().__init__()
for k,v in INITS.items():
self[k] = v
def _dict_set(dp, key, value):
dp[key] = value
for item in INITS.keys():
setattr(
DP,
item,
lambda key: property(
lambda self: self[key], lambda self, value: _dict_set(self, key, value)
)(item)
)
a = DP()
print(a) # {'ab': 100, 'cd': 200}
a.ab = 'ab100'
a.cd = False
print(a.ab, a.cd) # ab100 False
你不需要使用属性。只需重写__setattr__,使其为只读。
class C(object):
def __init__(self, keys, values):
for (key, value) in zip(keys, values):
self.__dict__[key] = value
def __setattr__(self, name, value):
raise Exception("It is read only!")
Tada。
>>> c = C('abc', [1,2,3])
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.d
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'd'
>>> c.d = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
Exception: It is read only!
>>> c.a = 'blah'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
Exception: It is read only!
如果需求是基于某些实例属性动态生成属性,那么下面的代码可能会有用:
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
对于那些来自搜索引擎的人,这里是我在谈论动态属性时寻找的两件事:
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()内置函数。