关于python:在classmethods上使用property()

Using property() on classmethods

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

1
2
3
4
5
6
7
8
9
class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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()函数与classmethod修饰函数一起使用?


属性在类上创建,但会影响实例。因此,如果需要ClassMethod属性,请在元类上创建该属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> 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

但是,由于您无论如何都在使用元类,所以如果您将类方法移到其中,它会读得更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 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初始值的元类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> 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


在阅读Python2.2发行说明时,我发现了以下内容。

The get method [of a property] won't be called when
the property is accessed as a class
attribute (C.x) instead of as an
instance attribute (C().x). If you
want to override the __get__ operation
for properties when used as a class
attribute, you can subclass property -
it is a new-style type itself - to
extend its __get__ method, or you can
define a descriptor type from scratch
by creating a new-style class that
defines __get__, __set__ and
__delete__ methods.

注意:下面的方法实际上不适用于setter,只适用于getter。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

但是,设置器实际上不工作:

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

foo._var是不变的,您只是用一个新值覆盖了属性。

您也可以使用ClassProperty作为装饰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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


Is it possible to use the property() function with classmethod decorated functions?

不。

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

由于实例是类的函数,可以从实例中派生类,因此可以从带有property的类属性中获得所需的任何行为:

1
2
3
4
5
6
7
8
9
10
11
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

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

1
2
3
4
5
6
7
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装饰器

通过对property子类化(它在C语言中实现,但在这里您可以看到等价的python),您实际上只需几行代码就可以创建一个classproperty修饰器:

1
2
3
4
5
6
7
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))

然后将decorator视为结合了属性的ClassMethod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

这段代码应该不会出错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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()

但我不确定这会有多明智。一篇旧的邮件列表文章建议它不应该工作。

使属性在类上工作:

上面的缺点是"class属性"不能从类中访问,因为它只会覆盖类__dict__中的数据描述符。

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

1
2
3
4
5
6
7
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

然后,元类的类实例可以有一个属性,该属性使用前面章节中演示的原理访问类的属性:

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

现在我们看到了这两个例子

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

和班级

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

有权访问类属性。


Python3!

老问题,很多视图,非常需要一个真正的python 3方式。

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

1
2
3
4
5
6
7
8
class FooProperties(type):

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

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

那么,>>> Foo.var

'FOO!'


没有合理的方法让这个"类属性"系统在Python中工作。

这是一个不合理的方法使它发挥作用。随着元类魔法的增加,你当然可以使它更加无缝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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所称的"描述符"。没有简单而简单的方法来解释这种元编程是如何工作的,所以我必须向您指出描述符how to。

只有在实现相当高级的框架时,才需要理解这类事情。像透明对象持久性或RPC系统,或者一种特定于域的语言。

然而,在对先前答案的评论中,你说你

need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.

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


如果您希望通过实例化的对象访问Class属性,那么只在元类上设置它并没有帮助,在这种情况下,您还需要在对象上安装一个普通的属性(它发送到Class属性)。我认为下面的内容更清楚一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/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


Because I need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.

您是否可以访问该类的至少一个实例?我可以想出一个办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

半个解决方案,在课堂上设置仍然不起作用。解决方案是一个自定义属性类,实现属性和静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

这里有一个解决方案,它可以通过类进行访问,也可以通过使用元类的实例进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

这也适用于在元类中定义的setter。


尝试一下,它可以在不需要更改/添加大量现有代码的情况下完成任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> 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包装器(它将实例作为第一个参数传递),一切都很好。


在搜索了不同的位置后,我找到了一个定义ClassProperty的方法对python 2和3有效。

1
2
3
4
5
6
7
8
9
10
11
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)

希望这能帮助别人:)


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

说真的。

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

如果你只是想改变这个值,一个属性并不是很有用,是吗?只需设置属性值并完成它。

只有在需要隐藏某些内容时才应使用属性,这些内容在将来的实现中可能会发生更改。

也许你的例子很简单,还有一些你遗漏了的地狱计算。但它看起来并没有增加显著的价值。

Java影响的"隐私"技术(在Python中,从属性开始的名称)并不是非常有用。谁的私人?当您拥有源代码时,private的意义有点模糊(就像在python中那样)。

Java影响的EJB风格的吸收器和定位器(通常以Python中的属性完成)有助于Java的原始自省,并通过静态语言编译器传递。所有这些getter和setter在Python中都没有那么有用。