在类方法上使用property()


173

我有一个带有两个类方法的类(使用classmethod()函数),用于获取和设置本质上是静态变量的东西。我试图将property()函数与这些函数一起使用,但是会导致错误。我能够在解释器中使用以下代码重现该错误:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

我可以演示类方法,但是它们不能用作属性:

>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable

是否可以将property()函数与装饰有类方法的函数一起使用?

Answers:


90

属性是在类上创建的,但会影响实例。因此,如果要使用classmethod属性,请在元类上创建该属性。

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         pass
...     @classmethod
...     def getvar(cls):
...         return cls._var
...     @classmethod
...     def setvar(cls, value):
...         cls._var = value
...     
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

但是由于无论如何都使用元类,所以只要将类方法移入其中,它就会更好看。

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         @property
...         def var(cls):
...             return cls._var
...         @var.setter
...         def var(cls, value):
...             cls._var = value
... 
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

或者,使用Python 3的metaclass=...语法,在foo类主体外部定义的元类,以及负责设置初始值的元类_var

>>> class foo_meta(type):
...     def __init__(cls, *args, **kwargs):
...         cls._var = 5
...     @property
...     def var(cls):
...         return cls._var
...     @var.setter
...     def var(cls, value):
...         cls._var = value
...
>>> class foo(metaclass=foo_meta):
...     pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

1
在Python 3.2中,这似乎对我不起作用。如果我将foo .__ metaclass __。var = property(foo.getvar.im_func,foo.setvar.im_func)更改为foo .__ metaclass __。var = property(foo.getvar .__ func__,foo.setvar .__ func__)我会得到“ AttributeError:类型执行“ foo.var”时,对象“ foo”没有属性“ var””。
Michael Kelley

SIGH双重更正:这适用于Python 2.7,但不适用于Python 3.2。
Michael Kelley

@MichaelKelley-那是因为在Python 3.x
mac

1
我不太确定是否了解,那么,编写此代码的Python 3.x方法是什么?
SylvainD 2014年

8
@Josay:您需要先定义元类,然后使用新class Foo(metaclass=...)语法定义类。
凯文(Kevin)

69

阅读Python 2.2发行说明,我发现以下内容。

当作为类属性(Cx)而不是实例属性(C()。x)访问该属性时,将不会调用[属性的] get方法。如果要在用作类属性时覆盖属性的__get__操作,则可以子类化属性-它本身是一种新型类型-扩展其__get__方法,或者可以通过创建新的属性来从头定义描述符类型风格的类,它定义__get __,__ set__和__delete__方法。

注意:以下方法实际上不适用于setter方法,仅适用于getter方法。

因此,我相信规定的解决方案是创建一个ClassProperty作为属性的子类。

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class foo(object):
    _var=5
    def getvar(cls):
        return cls._var
    getvar=classmethod(getvar)
    def setvar(cls,value):
        cls._var=value
    setvar=classmethod(setvar)
    var=ClassProperty(getvar,setvar)

assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3

但是,设置员实际上不起作用:

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var 保持不变,您只需用新值覆盖属性即可。

您还可以ClassProperty用作装饰器:

class foo(object):
    _var = 5

    @ClassProperty
    @classmethod
    def var(cls):
        return cls._var

    @var.setter
    @classmethod
    def var(cls, value):
        cls._var = value

assert foo.var == 5

20
我不认为ClassProperty的setter部分实际上按所述方式工作:尽管示例的断言全部通过,但最后foo._var == 4(隐含地不是3)。设置属性会破坏属性本身。当在python-dev 上讨论类属性时,有人指出,尽管获取器很琐碎,但没有元类的设置器很困难(不可能?)
Gabriel Grant

4
@加布里埃尔完全正确。我无法相信两年来没有人指出这一点。
2011年

我也不确定为什么您不只在这里使用self.fget(owner)和删除@classmethod全部使用a的需要?(这就是classmethod ,翻译.__get__(instance, owner)(*args, **kwargs)function(owner, *args, **kwargs)电话,通过中介,性能不需要中介)。
马丁·彼得斯

您的演示在getter或setter中都没有任何实际的转换,可以巧妙地证明您的foo.var = 3赋值实际上并没有通过property,而是只是将属性对象替换为foo一个整数。如果assert isinstance(foo.__dict__['var'], ClassProperty)在断言之间添加了调用,则会看到foo.var = 3执行后失败。
马丁·彼得斯

1
Python类不支持的描述符设置绑定类本身只有在获取时,(所以instance.attrinstance.attr = value并且del instance.attr将所有绑定的描述中发现的type(instance),但同时classobj.attr结合,classobj.attr = value并且del classobj.attr没有,而是替换或删除描述符对象本身)。您需要一个元类来支持设置和删除(使类对象成为实例,并使元类成为类型)。
马丁·彼得斯

56

我希望这个简单的只读@classproperty装饰器可以帮助寻找类属性的人。

class classproperty(object):

    def __init__(self, fget):
        self.fget = fget

    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class C(object):

    @classproperty
    def x(cls):
        return 1

assert C.x == 1
assert C().x == 1

2
这对子类有用吗?(子类可以覆盖classproperties吗?)
zakdances 2013年

1
嗯是吗 class D(C): x = 2; assert D.x == 2
dtheodor 2014年

我希望当我.format像这样使用它时可以工作"{x}".format(**dict(self.__class__.__dict__, **self.__dict__)):(
2rs2ts 2015年

@Nathan不仅...设置时,您还通过覆盖所有x访问 10。我喜欢这种方法,因为它既简洁又简单,但是听起来像是一种反模式
Michele d'Amico

轻松修复:添加一个__set__引发的值ValueError以防止覆盖。
Kiran Jonnalagadda '18

30

是否可以将property()函数与装饰有类方法的函数一起使用?

没有。

但是,类方法只是从类的实例可访问的类上的绑定方法(部分函数)。

由于实例是类的函数,并且您可以从实例派生该类,因此您可以使用property

class Example(object):
    _class_property = None
    @property
    def class_property(self):
        return self._class_property
    @class_property.setter
    def class_property(self, value):
        type(self)._class_property = value
    @class_property.deleter
    def class_property(self):
        del type(self)._class_property

此代码可用于测试-它应该通过而不会引起任何错误:

ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')

请注意,我们根本不需要元类-而且您也不会直接通过其类的实例直接访问元类。

写一个@classproperty装饰

实际上,您可以classproperty通过子类化在几行代码中创建一个装饰器property(它是用C实现的,但是您可以在此处看到等效的Python ):

class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)
    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)
    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

然后,将装饰器视为结合了属性的类方法:

class Foo(object):
    _bar = 5
    @classproperty
    def bar(cls):
        """this is the bar attribute - each subclass of Foo gets its own.
        Lookups should follow the method resolution order.
        """
        return cls._bar
    @bar.setter
    def bar(cls, value):
        cls._bar = value
    @bar.deleter
    def bar(cls):
        del cls._bar

这段代码应该可以正常工作:

def main():
    f = Foo()
    print(f.bar)
    f.bar = 4
    print(f.bar)
    del f.bar
    try:
        f.bar
    except AttributeError:
        pass
    else:
        raise RuntimeError('f.bar must have worked - inconceivable!')
    help(f)  # includes the Foo.bar help.
    f.bar = 5

    class Bar(Foo):
        "a subclass of Foo, nothing more"
    help(Bar) # includes the Foo.bar help!
    b = Bar()
    b.bar = 'baz'
    print(b.bar) # prints baz
    del b.bar
    print(b.bar) # prints 5 - looked up from Foo!

    
if __name__ == '__main__':
    main()

但是我不确定这将是多么明智。旧的邮件列表文章建议它不起作用。

使该属性在课程上起作用:

上面的缺点是无法从类中访问“类属性”,因为它只会覆盖类中的数据描述符__dict__

但是,我们可以使用metaclass中定义的属性来覆盖它__dict__。例如:

class MetaWithFooClassProperty(type):
    @property
    def foo(cls):
        """The foo property is a function of the class -
        in this case, the trivial case of the identity function.
        """
        return cls

然后,元类的类实例可以具有一个属性,该属性使用前面部分中已经说明的原理来访问类的属性:

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

现在我们看到两个实例

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

和班级

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

有权访问class属性。


3
清晰,简洁,透彻:这应该是公认的答案。
pfabri

25

Python 3!

老问题,很多观点,迫切需要一种真正的Python 3方法。

幸运的是,使用metaclasskwarg 很容易:

class FooProperties(type):

    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

然后, >>> Foo.var

“ F!”


1
也就是说,没有简单的方法可以直接使用
mehmet

@mehmet这不简单吗? Foo是其元类的实例,并且@property可以用于其方法,就像可以用于的实例Foo
OJFord

2
您必须为一个类定义另一个类,假设元类不可重用,那么复杂度就会增加一倍。
mehmet

一个类方法可以在类和实例中使用。此属性仅适用于该类。我不认为这是所要的。
亚伦·霍尔

1
@AaronHall如果重要的话,可以轻松添加到中Foo.__new__。尽管在那时可能值得使用getattribute代替,或者质疑是否假装某种语言功能确实是您想采用的方法。
OJFord

16

没有合理的方法可以使此“类属性”系统在Python中运行。

这是使其工作的一种不合理的方法。当然,您可以通过增加大量的元类魔术来使其变得更加无缝。

class ClassProperty(object):
    def __init__(self, getter, setter):
        self.getter = getter
        self.setter = setter
    def __get__(self, cls, owner):
        return getattr(cls, self.getter)()
    def __set__(self, cls, value):
        getattr(cls, self.setter)(value)

class MetaFoo(type):
    var = ClassProperty('getvar', 'setvar')

class Foo(object):
    __metaclass__ = MetaFoo
    _var = 5
    @classmethod
    def getvar(cls):
        print "Getting var =", cls._var
        return cls._var
    @classmethod
    def setvar(cls, value):
        print "Setting var =", value
        cls._var = value

x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x

问题的根源在于属性是Python所谓的“描述符”。没有简单快捷的方法来解释这种元编程的工作原理,因此我必须将您指向描述符howto

如果您要实现相当高级的框架,则只需要了解这种情况即可。就像透明的对象持久性或RPC系统,或一种特定于域的语言。

但是,在对上一个答案的评论中,您说

需要修改一个属性,该属性可以被类的所有实例看到,并且在调用这些类方法的范围内,该属性不具有对该类所有实例的引用。

在我看来,您真正想要的是观察者设计模式。


我喜欢代码示例的想法,但在实践中似乎有点笨拙。
Mark Roddy

我要完成的工作是非常简单的设置,并获得一个用作标记以修改所有实例的行为的单个属性,因此我认为Observer对于我想做的事情会被夸大。如果存在多个问题,那我会更倾向于。
Mark Roddy

似乎只是公开这些功能并直接调用它们是简化列表解决方案。我很好奇我做错了什么还是尝试做不到。抱歉,多条评论。300个字符的限制糟透了。
Mark Roddy

该代码示例的妙处在于,您可以一次实现所有笨拙的位,然后继承它们。只需将_var移动到派生类中。D1(Foo)类:_var = 21 D2(Foo)类_var =“ Hello” D1.var 21 D2.var你好
Thomas L Holaday

6

如果您想通过实例化的对象访问class属性,则仅在meta类上设置它无济于事,在这种情况下,您还需要在该对象上安装一个常规属性(该属性将分派到class属性)。我认为以下内容更加清楚:

#!/usr/bin/python

class classproperty(property):
    def __get__(self, obj, type_):
        return self.fget.__get__(None, type_)()

    def __set__(self, obj, value):
        cls = type(obj)
        return self.fset.__get__(None, cls)(value)

class A (object):

    _foo = 1

    @classproperty
    @classmethod
    def foo(cls):
        return cls._foo

    @foo.setter
    @classmethod
    def foo(cls, value):
        cls.foo = value

a = A()

print a.foo

b = A()

print b.foo

b.foo = 5

print a.foo

A.foo = 10

print b.foo

print A.foo

3

半个解决方案,在类上__set__仍然无效。解决方案是实现属性和静态方法的自定义属性类

class ClassProperty(object):
    def __init__(self, fget, fset):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget()

    def __set__(self, instance, value):
        self.fset(value)

class Foo(object):
    _bar = 1
    def get_bar():
        print 'getting'
        return Foo._bar

    def set_bar(value):
        print 'setting'
        Foo._bar = value

    bar = ClassProperty(get_bar, set_bar)

f = Foo()
#__get__ works
f.bar
Foo.bar

f.bar = 2
Foo.bar = 3 #__set__ does not

3

因为我需要修改一个属性,使得该属性可以被类的所有实例看到,并且在调用这些类方法的范围内,不能引用该类的所有实例。

您是否有权访问该类的至少一个实例?我可以想到一种方法:

class MyClass (object):
    __var = None

    def _set_var (self, value):
        type (self).__var = value

    def _get_var (self):
        return self.__var

    var = property (_get_var, _set_var)

a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var

2

试试看,无需更改/添加大量现有代码即可完成工作。

>>> class foo(object):
...     _var = 5
...     def getvar(cls):
...         return cls._var
...     getvar = classmethod(getvar)
...     def setvar(cls, value):
...         cls._var = value
...     setvar = classmethod(setvar)
...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3

property函数需要两个callable参数。给他们lambda包装器(它将实例作为第一个参数传递),一切都很好。


正如FlorianBösch指出的那样,(第三方库或旧版代码所要求的)语法为foo.var。
Thomas L Holaday

2

这是一个既可以通过类访问又可以通过使用元类的实例访问的解决方案。

In [1]: class ClassPropertyMeta(type):
   ...:     @property
   ...:     def prop(cls):
   ...:         return cls._prop
   ...:     def __new__(cls, name, parents, dct):
   ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
   ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
   ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
   ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
   ...:

In [2]: class ClassProperty(object):
   ...:     __metaclass__ = ClassPropertyMeta
   ...:     _prop = 42
   ...:     def __getattr__(self, attr):
   ...:         raise Exception('Never gets called')
   ...:

In [3]: ClassProperty.prop
Out[3]: 42

In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1

AttributeError: can't set attribute

In [5]: cp = ClassProperty()

In [6]: cp.prop
Out[6]: 42

In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1

<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
      6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
      7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
      9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)

AttributeError: can't set attribute

这也适用于在元类中定义的设置器。


1

在搜索了不同的位置后,我找到了一种方法,该方法定义一个在Python 2和3中有效的类属性。

from future.utils import with_metaclass

class BuilderMetaClass(type):
    @property
    def load_namespaces(self):
        return (self.__sourcepath__)

class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
    __sourcepath__ = 'sp'        

print(BuilderMixin.load_namespaces)

希望这可以帮助某人:)


1
如果您在某处找到此方法,则最好提供一个链接(请参阅如何参考他人撰写的材料
Andrew Myers

-27

这是我的建议。不要使用类方法。

说真的

在这种情况下使用类方法的原因是什么?为什么不具有普通类的普通对象?


如果您只是想更改值,那么属性不是真的很有帮助吗?只需设置属性值并完成操作即可。

仅在需要隐藏某些内容时才使用属性-在将来的实现中可能会更改的内容。

也许您的示例被精简了,但您还没有进行一些令人毛骨悚然的计算。但是,该属性看起来并没有带来明显的价值。

受Java影响的“隐私”技术(在Python中,以_开头的属性名称)并不是很有帮助。私人来自谁?当您拥有源代码时(就像在Python中一样),私有点有点模糊。

Java风格的EJB风格的getter和setter(通常在Python中作为属性完成)在那里可以促进Java的原始自省以及将静态语言编译器传递到集合。所有这些getter和setter在Python中都没有帮助。


14
因为我需要修改一个属性,使得该属性可以被类的所有实例看到,并且在调用这些类方法的范围内,不能引用该类的所有实例。
Mark Roddy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.