python - 为什么在类上设置描述符会覆盖描述符?

标签 python cpython python-descriptors

简单重现:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

class B(object):
    v = VocalDescriptor()

B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor

这个问题有一个 effective duplicate ,但重复的内容没有得到解答,我进一步挖掘了 CPython 源代码作为学习练习。警告:我进入了杂草丛中。我真的希望我能得到a captain who knows those waters的帮助。为了我自己的 future 和 future 读者的利益,我试图尽可能明确地追踪我正在查看的调用。

我见过很多关于 __getattribute__ 行为的墨水。应用于描述符,例如查找优先级。 "Invoking Descriptors" 中的 Python 片段就在For classes, the machinery is in type.__getattribute__()...下方在我看来大致符合我认为相应的CPython sourcetype_getattro ,我通过查看 "tp_slots" 找到了它然后where tp_getattro is populated 。事实上B.v最初打印 __get__, obj=None, objtype=<class '__main__.B'>对我来说很有意义。

我不明白的是,为什么分配 B.v = 3盲目地覆盖描述符,而不是触发v.__set__ ?我尝试跟踪 CPython 调用,再次从 "tp_slots" 开始,然后查看where tp_setattro is populated ,然后查看type_setattrotype_setattro appears to be _PyObject_GenericSetAttrWithDict 周围的薄 wrapper 。这就是我困惑的症结所在:_PyObject_GenericSetAttrWithDict似乎有logic that gives precedence to a descriptor's __set__ method !!考虑到这一点,我无法弄清楚为什么B.v = 3盲目覆盖v而不是触发v.__set__ .

免责声明 1:我没有使用 printfs 从源代码重建 Python,所以我不是 完全确定type_setattroB.v = 3 期间所调用的内容.

免责声明2:VocalDescriptor无意举例说明“典型”或“推荐”描述符定义。告诉我何时调用方法是一个冗长的无操作。

最佳答案

你是对的 B.v = 3只是用一个整数覆盖描述符(正如它应该的那样)。在描述符协议(protocol)中, __get__ is designed to be called as instance attribute or class attribute ,但是__set__被设计为仅作为实例属性调用。

对于B.v = 3要调用描述符,描述符应该已在元类上定义,即 type(B) .

>>> class BMeta(type): 
...     v = VocalDescriptor() 
... 
>>> class B(metaclass=BMeta): 
...     pass 
... 
>>> B.v = 3 
__set__

调用 B 上的描述符,您将使用一个实例:B().v = 3会做的。

B.v的原因调用 getter 还可以允许用户定制 B.v确实如此,独立于任何B().v做。一种常见的模式是允许直接访问描述符实例,方法是在使用类属性访问时返回描述符本身:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        if obj is None:
            return self
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

现在B.v会返回像 <mymodule.VocalDescriptor object at 0xdeadbeef> 这样的实例您可以与之互动。它实际上是描述符对象,定义为类属性,及其状态 B.v.__dict__B 的所有实例之间共享.

当然,这取决于用户的代码来准确定义他们想要的B.v要做,返回self只是常见的模式。一个 classmethod 是一个描述符的示例,它在这里执行不同的操作,请参阅 Descriptor HowTo Guide对于 classmethod 的纯 python 实现.

__get__不同,可用于自定义B().vB.v独立地,__set__除非属性访问是在实例上,否则不会被调用。我认为定制的目标B().v = otherB.v = other使用相同的描述符v并不常见或有用,不足以使描述符协议(protocol)进一步复杂化,特别是因为后者仍然可以使用元类描述符,如BMeta.v所示如上所述。

关于python - 为什么在类上设置描述符会覆盖描述符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57273021/

相关文章:

python - 常量折叠的具体规则是什么?

Python:有关 Python 函数实现的信息

python - 将 Cython 类对象作为参数传递给 C 函数

python - 了解 Python 描述符

python - 将方法限制为仅在实例上使用是否存在危险?

python - 如何将一系列对象转换为单个 DataFrame?

python - 如何将 Flask/WTForms SelectField 添加到我的 HTML 中?

python - PIL : can't save the jpg pasted with a png

Python 属性和描述符

python - 迷宫里的小偷