在类方法上使用property()

308 投票
19 回答
168589 浏览
提问于 2025-04-11 09:25

我有一个类,这个类里面有两个类方法(使用 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() 函数与用 @classmethod 装饰的函数一起使用?

19 个回答

108

我希望这个超级简单的只读 @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
151

更新: 在 Python 3.13 中,连用 @classmethod@property 的功能被 移除了

Python 3.9 时,你可以把这两个一起用,但正如 @xgt 在 评论 中提到的,这种用法在 Python 3.11 中被 弃用了,所以 现在不再支持(不过可能在一段时间内还能用,或者将来会重新引入)。

你可以查看这里的版本说明:

https://docs.python.org/3.11/library/functions.html#classmethod

不过,之前的用法是这样的:

class G:
    @classmethod
    @property
    def __doc__(cls):
        return f'A doc for {cls.__name__!r}'

顺序很重要 - 由于描述符的交互方式,@classmethod 必须放在最上面。

194

3.8 < Python < 3.11

可以同时使用这两个装饰器。具体可以参考 这个回答

Python 2 和 Python 3(在 3.9-3.10 也适用)

在一个类上创建了一个属性,但这个属性影响的是类的实例。所以如果你想要一个 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

撰写回答