简单重现:
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 source在type_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_setattro 。 type_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_setattro
是 B.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().v
和B.v
独立地,__set__
除非属性访问是在实例上,否则不会被调用。我认为定制的目标B().v = other
和B.v = other
使用相同的描述符v
并不常见或有用,不足以使描述符协议(protocol)进一步复杂化,特别是因为后者仍然可以使用元类描述符,如BMeta.v
所示如上所述。
关于python - 为什么在类上设置描述符会覆盖描述符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57273021/