将描述符或属性与类方法一起使用

Using descriptors or properties with classmethod

我看过很多关于这个问题的答案,他们都说不可能将 属性 与类方法一起使用,但以下代码有效:

class A:
    @classmethod
    @property
    def hello(cls):
        print(1234)

>>> A.hello
1234

为什么以及如何工作?

运行 在 CPython 3.9.1 上。

从 Python 3.9 开始,class方法触发 descriptor protocol. From the Python docs:

The code path for hasattr(obj, '__get__') was added in Python 3.9 and makes it possible for classmethod() to support chained decorators.

令人惊讶的是,深入研究该主题会告诉您 classmethod 触发描述符 __get__,其中 class 本身作为实例:

class Descriptor:
  def __get__(self, instance, owner):
    print(instance, owner)
  def __set__(self, value, owner):
    print(value, owner)

class A:
  regular = Descriptor()
  clsmethod = classmethod(Descriptor())

>>> A.regular
None <class '__main__.A'>
>>> A.clsmethod
<class '__main__.A'> None

我猜他们是专门为支持 @property 等描述符而制作的,因为通过 class returns 属性 本身访问它们:

class B:
  @property
  def prop(self):
    print(self)

>>> B.__dict__["prop"].__get__(None, 1234)
<property object at 0x000001BEEB635630>
>>> B.__dict__["prop"].__get__(1234, None)
1234

如果您希望同时支持 classmethod 和普通描述符,这有点不直观并且会使描述符协议变得笨拙,因为您必须检查 owner 是否为 None

但是请记住,__set__ 未被调用 (因为描述符协议在设置 class 属性时不会调用它),使您无法使用@property.setter:

>>> A.regular = 1234
>>> A.regular
1234
>>> A.clsmethod = 1234
>>> A.clsmethod
1234