我有一个带有两个类方法的类(使用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

是否可以使用属性()函数与@classmethod装饰函数?


当前回答

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

>>> 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

属性函数需要两个可调用参数。给它们lambda包装器(它将实例作为第一个参数传递),一切都很好。

其他回答

3.8 < Python < 3.11

可以同时使用这两种装饰器。请看这个答案。

Python < 3.9

属性在类上创建,但会影响实例。所以如果你想要一个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的元类=…语法,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

我希望这个极其简单的只读@classproperty装饰器能够帮助人们查找类属性。

class classproperty(property):
    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

Python 3 !

参见@Amit Portnoy的回答,在python >= 3.9中有一个更清晰的方法


老问题,很多观点,迫切需要一个唯一正确的Python 3方式。

幸运的是,使用元类kwarg很简单:

class FooProperties(type):
    
    @property
    def var(cls):
        return cls._var

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

然后>>> foo。var

“喷火!”

我找到了一个解决这个问题的简单方法。它是一个叫做classutilities的包(pip install classutilities),请参阅这里关于PyPi的文档。

考虑的例子:

import classutilities

class SomeClass(classutilities.ClassPropertiesMixin):
    _some_variable = 8  # Some encapsulated class variable

    @classutilities.classproperty
    def some_variable(cls):  # class property getter
        return cls._some_variable

    @some_variable.setter
    def some_variable(cls, value):  # class property setter
        cls._some_variable = value

你可以在类级和实例级使用它:

# Getter on class level:
value = SomeClass.some_variable
print(value)  # >>> 8
# Getter on instance level
inst = SomeClass()
value = inst.some_variable
print(value)  # >>> 8

# Setter on class level:
new_value = 9
SomeClass.some_variable = new_value
print(SomeClass.some_variable)   # >>> 9
print(SomeClass._some_variable)  # >>> 9
# Setter on instance level
inst = SomeClass()
inst.some_variable = new_value
print(SomeClass.some_variable)   # >>> 9
print(SomeClass._some_variable)  # >>> 9
print(inst.some_variable)        # >>> 9
print(inst._some_variable)       # >>> 9

如您所见,它在所有情况下都能正常工作。

这里有一个解决方案,它应该既适用于通过类访问,也适用于通过使用元类的实例访问。

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。