Python 3.11+ 中的类属性

kg5*_*583 18 python properties python-3.x python-decorators python-descriptors

@classmethod在 Python 3.9 中,我们获得了链接和@property合理创建类属性的能力。

\n
class Foo:\n  @property\n  def instance_property(self):\n    return "A regular property"\n\n  @classmethod\n  @property\n  def class_property(cls):\n    return "A class property"\n
Run Code Online (Sandbox Code Playgroud)\n

这是通过与描述符协议进行适当的交互来实现的@classmethod,这意味着一个人的前景不仅限于@property阳光下的任何描述符。一切都很好,直到发现该实现导致了“许多下游问题”,并在 Python 3.11 中弃用。

\n

我已经阅读了一些关于弃用的GitHub 讨论,并且不会在这里抱怨我所说的仓促撤回仓促设计。事实上,类属性是人们想要的合理的东西,并且可以在 Python 3.9/3.10 中使用,但现在不能。发行说明建议如下:

\n
\n

要 \xe2\x80\x9cpass-through\xe2\x80\x9d 类方法,请考虑使用 Python 3.10 中添加的 __wrapped__ 属性。

\n
\n

如果说这样的句子本身极其无益,那就不会引起争议。描述符协议不是普通用户需要或想要遇到的东西,因此@classmethod通过自定义实现与它们链接肯定是那些了解情况的人可以并且会花时间弄清楚如何在 3.11+ 中正确执行的操作。

\n

但是对于那些不知道@property除了允许他们删除括号的东西之外还有什么的人来说,如何在 Python 3.11+ 中定义类属性,特别是如何做得好?

\n

jsb*_*eno 5

尽管如此,就像我在 3.9 之前所做的那样:自定义“属性”重写。

问题是,“属性”可以做很多事情,如果需要其中的所有内容,那就需要很多代码。

我想可以直接子类化property它自己,这样我们就可以获得一个额外的.class_getter装饰器。

显然,类设置器将涉及自定义元类或__setattr__.

让我们看看我是否可以提供一个相当短的classproperty.

[稍微修改了一下之后]

因此,事实证明,简单地继承属性并为“类 getter”添加装饰器并不容易实现——“属性”在编写时并没有考虑到子类化和扩展其功能。

因此,“简单”的事情和子集是编写一个自定义描述符装饰器,它只会将单个方法转换为 classgetter - 并且根本不支持 set、del 或继承。

另一方面,代码又短又简单:

class classproperty:
    def __init__(self, func):
        self.fget = func
    def __get__(self, instance, owner):
        return self.fget(owner)
Run Code Online (Sandbox Code Playgroud)

这就像预期的那样工作:


In [19]: class A:
    ...:     @classproperty
    ...:     def test(cls):
    ...:         return f"property of {cls.__name__}"
    ...: 

In [20]: A.test
Out[20]: 'property of A'

Run Code Online (Sandbox Code Playgroud)

另一种方式,如果想要一直拥有一个类属性设置器,只需在自定义元类上编写一个普通属性(该元类可以仅用于保存所需的属性)。然而,这种方法将使属性在实例上不可见 - 它们仅适用于类本身:


In [22]: class MetaA(type):
    ...:     @property
    ...:     def test(cls):
    ...:         return cls._test
    ...:     @test.setter
    ...:     def test(cls, value):
    ...:         cls._test = value.upper()
    ...: 

In [23]: class A(metaclass=MetaA):
    ...:     pass
    ...: 

In [24]: A.test = "hello world!"

In [25]: A.test
Out[25]: 'HELLO WORLD!'

In [26]: A().test
--------------------------------------------------------------
...
AttributeError: 'A' object has no attribute
Run Code Online (Sandbox Code Playgroud)