类属性在Python 3.11+中

huangapple go评论81阅读模式
英文:

Class properties in Python 3.11+

问题

In Python 3.9,我们获得了使用@classmethod@property链式创建类属性的能力。

class Foo:
  @property
  def instance_property(self):
    return "一个常规属性"

  @classmethod
  @property
  def class_property(cls):
    return "一个类属性"

这是通过使@classmethod与描述符协议适当交互来实现的,这意味着您不仅仅局限于@property,而是可以使用太阳下的任何描述符。一切都很顺利,直到发现该实现引发了"一系列下游问题",并在Python 3.11中取消了。

我已经阅读了与取消相关的GitHub讨论一些内容,在这里我不会抱怨我认为是匆忙设计的匆忙撤销。事实是,类属性是人们在Python 3.9/3.10中想要并且可以使用的合理事物,但现在却不能使用了。发布说明建议如下:

若要“透传”一个classmethod,请考虑使用Python 3.10中添加的__wrapped__属性。

毫无争议的是,单独使用这样的句子非常没有帮助。描述符协议不是您的普通用户需要或希望遇到的东西,因此通过自定义实现通过它们与@classmethod链接在一起,肯定是那些了解情况并愿意花时间弄清楚如何正确实现的人可以做的事情。

但对于那些除了让他们去掉括号的东西外,对@property一无所知的人,如何在Python 3.11+中定义类属性,特别是如何做到_良好_?

英文:

In Python 3.9, we gained the ability to chain @classmethod and @property to sensibly create class properties.

class Foo:
  @property
  def instance_property(self):
    return "A regular property"

  @classmethod
  @property
  def class_property(cls):
    return "A class property"

This was enabled by giving @classmethod proper interaction with the descriptor protocol, meaning one's prospects were not limited to @property but any descriptor under the sun. Everything was fine and dandy until it was discovered that the implementation led to "a number of downstream problems", with deprecation coming in Python 3.11.

I've read over the GitHub discussions concerning the deprecation a bit and will not gripe here about what I would call a hasty retraction to a hasty design. The fact of the matter is that class properties are a reasonable thing that people want and could use in Python 3.9/3.10, but now can't. The release notes suggest the following:

> To “pass-through” a classmethod, consider using the __wrapped__ attribute that was added in Python 3.10.

It would not be controversial to call such a sentence extremely unhelpful on its own. The descriptor protocol is not something your average user will ever need to or want to encounter, and thus chaining @classmethod with them via a custom implementation is surely something that those in the know could and would spend time figuring out how to properly do in 3.11+.

But for those who have no idea what @property is besides that thing that lets them drop parentheses, how do you define class properties in Python 3.11+, and, in particular, how do you do it well?

答案1

得分: 3

Here's the translated content:

就像我以前在3.9之前一直做的那样,尽管如此:自定义“property”重写。

问题是,“property”做了很多事情,如果一个人需要其中的所有功能,那将是大量的代码。

我想可以直接子类化property本身,这样我们就可以得到额外的.class_getter装饰器。

显然,类setter要么涉及到自定义元类,要么涉及到__setattr__的特殊化。

让我们看看我能否创建一个相对简短的classproperty

[经过一些尝试后]

事实证明,简单地继承property并添加一个“class getter”装饰器并不容易实现 - “property”不是以子类化和扩展其功能为目标编写的。

因此,较“容易”的方法是编写一个自定义的描述符装饰器,它只会将单个方法转换为类getter - 没有设置、删除或继承支持。

另一方面,这段代码很简短和简单:

class classproperty:
    def __init__(self, func):
        self.fget = func
    def __get__(self, instance, owner):
        return self.fget(owner)

这就像预期的那样工作:


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

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

如果有人想要一直到具有类属性设置器,那么只需在自定义元类上编写一个普通的property(它可以仅用于保存所需的属性)。然而,这种方法将使属性在实例上变得不可见 - 它们只会在类本身上工作:


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

Is there anything else you would like to be translated?

英文:

Like I always did before 3.9, nonetheless: a custom "property" rewrite.

The problem is, "property" does a lot of things, and if one will need everything its in there, it is a lot of code.

I guess it is possible to just subclass property itself, so that we can get an extra .class_getter decorator.

A class setter, obviously, would involve either a custom metaclass or an especialisation of __setattr__.

Let's see if I can come with a reasonably short classproperty.

[after a bit trials]

So, it turns out simply inheriting property and adding a decorator for a "class getter" is not easily feasible - "property" is not written with subclassing and expanding its functionality in mind.

Therefore, the "easy" thing, and subset is to write a custom descriptor decorator, which will just convert a single method into a classgetter - and no set, del or inheritance support at all.

On the other hand, the code is short and simple:

class classproperty:
    def __init__(self, func):
        self.fget = func
    def __get__(self, instance, owner):
        return self.fget(owner)

And this simply works as expected:


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

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

Another way, if one wants to go all the way to have a class attribute setter, it is a matter of writing a plain property on a custom metaclass (which can exist just for holding the desired properties). This approach however will render the properties invisible on the instances - they will work only on the class itself:


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

huangapple
  • 本文由 发表于 2023年5月15日 04:55:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76249636.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定