如何动态地向类添加属性?

Ant*_*ong 193 python monkeypatching runtime properties

目标是创建一个类似于db结果集的模拟类.

因此,例如,如果数据库查询使用dict表达式返回{'ab':100, 'cd':200},那么我想看到:

>>> dummy.ab
100
Run Code Online (Sandbox Code Playgroud)

起初我想也许我可以这样做:

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
Run Code Online (Sandbox Code Playgroud)

但是c.ab返回一个属性对象.

更换setattr线路k = property(lambda x: vs[i])完全没用.

那么在运行时创建实例属性的正确方法是什么?

PS我知道如何__getattribute__使用该方法?

Eev*_*vee 309

我想我应该扩大这个答案,现在我已经老了,更聪明,知道发生了什么.迟到总比不到好.

可以动态地向类添加属性.但这就是问题:你必须把它添加到课堂上.

>>> class Foo(object):
...     pass
... 
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4
Run Code Online (Sandbox Code Playgroud)

A property实际上是一个称为描述符的简单实现.它是一个为给定类提供给定属性的自定义处理的对象.有点像一种将if大树考虑在内的方法__getattribute__.

当我要求foo.b在上面的例子中,Python看到的是,b在该类中定义的实现描述符协议哪位只是意味着它是一个对象__get__,__set____delete__方法.描述符声称负责处理该属性,因此Python调用Foo.b.__get__(foo, Foo),并且返回值作为属性的值传递给您.在的情况下property,每一种方法只是调用fget,fsetfdel你传递给property构造函数.

描述符实际上是Python公开其整个OO实现的管道的方式.事实上,还有另一种类型的描述符甚至更常见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>
Run Code Online (Sandbox Code Playgroud)

简陋的方法只是另一种描述符.它__get__的调用实例作为第一个参数固定物; 实际上,它这样做:

def __get__(self, instance, owner):
    return functools.partial(self.function, instance)
Run Code Online (Sandbox Code Playgroud)

无论如何,我怀疑这就是为什么描述符只适用于类:它们是首先为类提供动力的东西的形式化.它们甚至是规则的例外:您显然可以将描述符分配给类,而类本身就是实例type!事实上,试图读取Foo.b静止的电话property.__get__; 当作为类属性访问时,描述符返回自己只是惯用的.

我认为几乎所有Python的OO系统都可以用Python表示,这很酷.:)

哦,如果你有兴趣的话,我前段时间写了一篇关于描述符罗嗦博客文章.

  • 无需添加add_property方法.`setattr(Foo,'name',property(func))` (29认同)
  • 你的"但这就是捕获......"只是为我节省了几个小时的工作量.谢谢. (7认同)
  • 您缺少将属性名称分配给描述符。你需要 `Foo.b.__set_name__(Foo, 'b')`。请参阅 [`object.__set_name__`](https://docs.python.org/3/reference/datamodel.html?highlight=__set_name__#object.__set_name__) 声明类 `Foo` 时会自动调用它,因此添加之后,属性会错过此调用。(3.6 中新增) (3认同)
  • 如果要在单个实例上定义属性,可以[在运行时创建一个类并修改\ _\__ class\_\_](https://gist.github.com/Wilfred/49b0409c6489f1bdf5a5c98a488b31b5). (2认同)
  • @myproperty.setter 怎么样?如何动态添加它? (2认同)

bob*_*nce 56

目标是创建一个类似于db结果集的模拟类.

那么你想要的是一个字典,你可以拼写一个['b']作为ab?

这很简单:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__
Run Code Online (Sandbox Code Playgroud)

  • 在更一般的设置中,这的用途有限。如果字典具有多级层次结构,例如 d = {'a1': {'b': 'c'}, 'a2': ...},那么虽然您可以执行 d.a1 或 d.a2,但您也可以'做 d.a1.b (2认同)

Eev*_*vee 34

看来你可以用a更简单地解决这个问题namedtuple,因为你提前了解了整个字段列表.

from collections import namedtuple

Foo = namedtuple('Foo', ['bar', 'quux'])

foo = Foo(bar=13, quux=74)
print foo.bar, foo.quux

foo2 = Foo()  # error
Run Code Online (Sandbox Code Playgroud)

如果您绝对需要编写自己的setter,则必须在类级别进行元编程; property()不适用于实例.

  • 对不起,这个答案充其量只适用于特殊情况,其中只有一个只有只读属性的类才提前知道.换句话说,我不认为它解决了如何在运行时将一般属性(不仅仅是只读属性)添加到类的更广泛的问题(另一个"附加"的当前版本也没有回答由作者发布). (4认同)
  • [适用于Python 2.4的命名元组配方](http://code.activestate.com/recipes/500261/) (3认同)
  • 编写`namedtuple`的人应该获得奖励,使其成为忠实的面向对象原则的流畅和优雅. (2认同)

Rya*_*yan 31

您不需要使用属性.只需覆盖__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!")
Run Code Online (Sandbox Code Playgroud)

田田.

>>> 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!
Run Code Online (Sandbox Code Playgroud)


Dav*_*son 7

这是一个解决方案:

  • 允许将属性名称指定为 strings,因此它们可以来自某些外部数据源,而不是全部列在您的程序中。
  • 在定义类时添加属性,而不是每次创建对象时添加

定义类后,您只需执行以下操作即可动态添加属性:

setattr(SomeClass, 'propertyName', property(getter, setter))
Run Code Online (Sandbox Code Playgroud)

这是一个完整的示例,在 Python 3 中测试过:

setattr(SomeClass, 'propertyName', property(getter, setter))
Run Code Online (Sandbox Code Playgroud)


Aar*_*all 6

如何动态地向python类添加属性?

假设您有一个要添加属性的对象.通常,我想在需要开始管理具有下游使用的代码中的属性的访问时使用属性,以便我可以维护一致的API.现在我通常会将它们添加到定义对象的源代码中,但我们假设您没有该访问权限,或者您需要以编程方式真正动态地选择函数.

创建一个类

使用基于文档property的示例,让我们创建一个具有"隐藏"属性的对象类并创建它的实例:

class C(object):
    '''basic class'''
    _x = None

o = C()
Run Code Online (Sandbox Code Playgroud)

在Python中,我们期望有一种明显的做事方式.但是,在这种情况下,我将展示两种方式:使用装饰符号,而不使用.首先,没有装饰符号.这对于getter,setter或deleters的动态分配可能更有用.

动态(又称猴子补丁)

让我们为我们的班级创建一些:

def getx(self):
    return self._x

def setx(self, value):
    self._x = value

def delx(self):
    del self._x
Run Code Online (Sandbox Code Playgroud)

现在我们将这些分配给该物业.请注意,我们可以在这里以编程方式选择我们的函数,回答动态问题:

C.x = property(getx, setx, delx, "I'm the 'x' property.")
Run Code Online (Sandbox Code Playgroud)

用法:

>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
>>> help(C.x)
Help on property:

    I'm the 'x' property.
Run Code Online (Sandbox Code Playgroud)

装饰

我们可以像上面用装饰符号那样做,但在这种情况下,我们必须将方法命名为同名(我建议保持它与属性相同),所以编程分配不是那么简单它使用上面的方法:

@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
Run Code Online (Sandbox Code Playgroud)

并将属性对象及其预配置的setter和deleters分配给该类:

C.x = x
Run Code Online (Sandbox Code Playgroud)

用法:

>>> 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
Run Code Online (Sandbox Code Playgroud)


tle*_*leb 6

对于那些来自搜索引擎的人,这里是我在谈论动态属性时要寻找的两件事:

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'
Run Code Online (Sandbox Code Playgroud)

__dict__如果您想放置动态创建的属性,这很好。__getattr__只在需要值时才做一些事情是好的,比如查询数据库。set/get 组合可以很好地简化对存储在类中的数据的访问(如上面的示例)。

如果您只需要一个动态属性,请查看property()内置函数。


Cre*_*esh 5

不确定我是否完全理解这个问题,但是您可以在运行时使用类的内置修改实例属性__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
Run Code Online (Sandbox Code Playgroud)


Ale*_*nor 5

您无法property()在运行时向实例添加新内容,因为属性是数据描述符.相反,您必须动态创建新类或重载__getattribute__,以便在实例上处理数据描述符.


kjf*_*tch 5

在这个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)
>>>
Run Code Online (Sandbox Code Playgroud)

您可以使用此变体来创建默认值,这是您的目标(在该问题中也有一个答案可以解决此问题).


小智 5

您可以使用以下代码使用字典对象更新类属性:

class ExampleClass():
    def __init__(self, argv):
        for key, val in argv.items():
            self.__dict__[key] = val

if __name__ == '__main__':
    argv = {'intro': 'Hello World!'}
    instance = ExampleClass(argv)
    print instance.intro
Run Code Online (Sandbox Code Playgroud)