如何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])replacesetattrk = 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构造函数的fgetfsetfdel

描述符实际上是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