如何dynamic添加属性到类中?
目标是创build一个类似db结果集的模拟类。
例如,如果一个数据库查询返回,使用一个字典expression式, {'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
但是c.ab
返回一个属性对象。
用k = property(lambda x: vs[i])
replacesetattr
行k = property(lambda x: vs[i])
根本没用。
那么在运行时创build实例属性的正确方法是什么?
PS我知道如何使用__getattribute__
方法提供一个替代scheme?
我想我应该扩大这个答案,现在我变老了,知道发生了什么事情。 迟到总比不到好。
您可以dynamic地将属性添加到类。 但是这是一个问题:你必须把它添加到课堂上 。
>>> class Foo(object): ... pass ... >>> foo = Foo() >>> foo.a = 3 >>> Foo.b = property(lambda self: self.a + 1) >>> foo.b 4
property
实际上是一个称为描述符的事物的简单实现。 这是一个对给定类的给定属性提供自定义处理的对象。 有点像从__getattribute__
树的方法。
当我在上面的例子中要求foo.b
时,Python发现在类上定义的b
实现了描述符协议 – 这意味着它是一个带有__get__
, __set__
或__delete__
方法的对象。 描述符声明了处理该属性的责任,所以Python调用Foo.b.__get__(foo, Foo)
,返回值作为属性的值传回给您。 在property
的情况下,这些方法中的每一个都会调用传递给property
构造函数的fget
, fset
或fdel
。
描述符实际上是Python公开其整个OO实现的pipe道的方式。 事实上,还有另一种types的描述比property
更普遍。
>>> class Foo(object): ... def bar(self): ... pass ... >>> Foo().bar <bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>> >>> Foo().bar.__get__ <method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>
卑微的方法只是另一种描述符。 它的__get__
作为第一个参数在调用实例上; 实际上,它是这样做的:
def __get__(self, instance, owner): return functools.partial(self.function, instance)
无论如何,我怀疑这就是为什么描述符只能在类上工作的原因:它们是把类放在首位的东西的forms化。 他们甚至是规则的例外:你可以明确地将描述符分配给一个类,而类本身就是type
实例! 实际上,试图读取Foo.b
仍然调用property.__get__
; 描述符在作为类属性访问时返回它们自己就是惯用的。
我认为Python的所有OO系统都可以用Python来expression,这真的很酷。 🙂
噢,如果你有兴趣的话,我写了一篇关于描述符的博客文章 。
目标是创build一个类似db结果集的模拟类。
那么你想要的是一个字典,你可以拼出一个['b']作为ab?
这很容易:
class atdict(dict): __getattr__= dict.__getitem__ __setattr__= dict.__setitem__ __delattr__= dict.__delitem__
看起来你可以更简单地用一个namedtuple
来解决这个问题,因为你提前知道整个字段列表。
from collections import namedtuple Foo = namedtuple('Foo', ['bar', 'quux']) foo = Foo(bar=13, quux=74) print foo.bar, foo.quux foo2 = Foo() # error
如果你绝对需要编写你自己的setter,你将不得不在课堂上进行元编程; property()
不适用于实例。
你不需要使用一个属性。 只要覆盖__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!")
田田。
>>> c = C('abc', [1,2,3]) >>> ca 1 >>> cb 2 >>> cc 3 >>> cd Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'C' object has no attribute 'd' >>> cd = 42 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in __setattr__ Exception: It is read only! >>> ca = 'blah' Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in __setattr__ Exception: It is read only!
您不能在运行时将新的property()
添加到实例,因为属性是数据描述符。 相反,您必须dynamic创build一个新的类,或者重载__getattribute__
来处理实例上的数据描述符。
我在这个Stack Overflow文章中提出了一个相似的问题来创build一个创build简单types的类工厂。 结果是这个答案有一个工厂的工作版本。 这是答案的一小部分:
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) >>>
你可以使用一些变化来创build默认值,这是你的目标(在这个问题中也有一个答案,它处理这个问题)。
不知道我是否完全理解了这个问题,但是可以使用类的内置__dict__
在运行时修改实例属性:
class C(object): def __init__(self, ks, vs): self.__dict__ = dict(zip(ks, vs)) if __name__ == "__main__": ks = ['ab', 'cd'] vs = [12, 34] c = C(ks, vs) print(c.ab) # 12
最好的方法是通过定义__slots__
。 这样你的实例不能有新的属性。
ks = ['ab', 'cd'] vs = [12, 34] class C(dict): __slots__ = [] def __init__(self, ks, vs): self.update(zip(ks, vs)) def __getattr__(self, key): return self[key] if __name__ == "__main__": c = C(ks, vs) print c.ab
这打印12
c.ab = 33
这给了: AttributeError: 'C' object has no attribute 'ab'
只是另一个例子如何达到预期的效果
class Foo(object): _bar = None @property def bar(self): return self._bar @bar.setter def bar(self, value): self._bar = value def __init__(self, dyn_property_name): setattr(Foo, dyn_property_name, Foo.bar)
所以现在我们可以做这样的事情:
>>> foo = Foo('baz') >>> foo.baz = 5 >>> foo.bar 5 >>> foo.baz 5
如何dynamic添加属性到一个python类?
假设你有一个对象,你想添加一个属性。 通常,当我需要开始pipe理对具有下游使用的代码中的属性的访问时,我想要使用属性,以便我可以维护一致的API。 现在我通常会将它们添加到定义对象的源代码中,但假设您没有该访问权限,或者您需要以编程方式真正dynamicselect您的函数。
创build一个class级
使用基于property
文档的示例,让我们创build一个具有“隐藏”属性的对象类,并创build它的一个实例:
class C(object): '''basic class''' _x = None o = C()
在Python中,我们希望有一个明显的做事方式。 然而,在这种情况下,我将展示两种方式:使用装饰符号和不使用。 首先,没有装饰符号。 这对于获取者,设置者或删除者的dynamic分配可能更有用。
dynamic(又名猴子修补)
让我们为我们的class级创build一些:
def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x
现在我们把这些分配给财产。 请注意,我们可以在这里以编程方式select我们的function,回答dynamic问题:
Cx = property(getx, setx, delx, "I'm the 'x' property.")
和用法:
>>> ox = 'foo' >>> ox 'foo' >>> del ox >>> print(ox) None >>> help(Cx) Help on property: I'm the 'x' property.
装饰
我们可以像上面用装饰符号一样来做同样的事情,但是在这种情况下,我们必须命名方法所有相同的名称(并且我build议保持它与属性相同),所以编程分配并不是那么微不足道它正在使用上面的方法:
@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分配给该类:
Cx = x
和用法:
>>> help(Cx) Help on property: I'm the 'x' property. >>> ox >>> ox = 'foo' >>> ox 'foo' >>> del ox >>> print(ox) None
对于那些来自search引擎的人来说,在讨论dynamic属性时,我一直在寻找两件事:
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__
是好的,如果你想把dynamic创build的属性。 __getattr__
是很好的只有当需要的值时,如查询数据库。 set / get组合可以简化对类中存储的数据的访问(如上例所示)。
如果你只想要一个dynamic属性,可以看看property()的内置函数。
只有dynamic附加属性的方法是用新属性创build一个新的类及其实例。
class Holder: p = property(lambda x: vs[i], self.fn_readonly) setattr(self, k, Holder().p)
这似乎工作(但见下文):
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__)
如果您需要更复杂的行为,请随时编辑您的答案。
编辑
对于大数据集,以下内容可能会更有效率:
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__)
为了回答你的问题的主要观点,你需要一个字典中的只读属性作为一个不可变的数据源:
目标是创build一个类似db结果集的模拟类。
所以例如,如果数据库查询返回,使用一个字典expression式,
{'ab':100, 'cd':200}
,那么我会看到>>> dummy.ab 100
我将演示如何使用collections
模块中的namedtuple
来实现这一点:
import collections data = {'ab':100, 'cd':200} def maketuple(d): '''given a dict, return a namedtuple''' Tup = collections.namedtuple('TupName', d.keys()) # iterkeys in Python2 return Tup(**d) dummy = maketuple(data) dummy.ab
返回100
class atdict(dict): def __init__(self, value, **kwargs): super().__init__(**kwargs) self.__dict = value def __getattr__(self, name): for key in self.__dict: if type(self.__dict[key]) is list: for idx, item in enumerate(self.__dict[key]): if type(item) is dict: self.__dict[key][idx] = atdict(item) if type(self.__dict[key]) is dict: self.__dict[key] = atdict(self.__dict[key]) return self.__dict[name] d1 = atdict({'a' : {'b': [{'c': 1}, 2]}}) print(d1.ab[0].c)
输出是:
>> 1
我最近遇到了一个类似的问题,我想出的解决scheme使用了__getattr__
和__setattr__
来处理我想要处理的属性,其他所有的东西都传递给了原始数据。
class C(object): def __init__(self, properties): self.existing = "Still Here" self.properties = properties def __getattr__(self, name): if "properties" in self.__dict__ and name in self.properties: return self.properties[name] # Or call a function, etc return self.__dict__[name] def __setattr__(self, name, value): if "properties" in self.__dict__ and name in self.properties: self.properties[name] = value else: self.__dict__[name] = value if __name__ == "__main__": my_properties = {'a':1, 'b':2, 'c':3} c = C(my_properties) assert ca == 1 assert c.existing == "Still Here" cb = 10 assert c.properties['b'] == 10