问题描述
简单的复制:
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
此问题具有有效重复,但没有回答重复项,我作为学习练习对CPython源代码进行了更多研究.警告:我进入了杂草.我真的很希望能从知道那些水域的船长那里得到帮助一个>.为了我自己的未来利益和未来读者的利益,我试图尽可能地明确地跟踪正在寻找的电话.
This question has an effective duplicate, but the duplicate was not answered, and I dug a bit more into the CPython source as a learning exercise. Warning: i went into the weeds. I'm really hoping I can get help from a captain who knows those waters. I tried to be as explicit as possible in tracing the calls I was looking at, for my own future benefit and the benefit of future readers.
我已经看到很多墨水洒在__getattribute__
应用于描述符的行为上,例如查找优先级. 调用描述符" 中位于我认为与相应的" tp_slots"然后在其中填充了tp_getattro .而B.v
最初会打印__get__, obj=None, objtype=<class '__main__.B'>
的事实对我来说很有意义.
I've seen a lot of ink spilled over the behavior of __getattribute__
applied to descriptors, e.g. lookup precedence. The Python snippet in "Invoking Descriptors" just below For classes, the machinery is in type.__getattribute__()...
roughly agrees in my mind with what I believe is the corresponding CPython source in type_getattro
, which I tracked down by looking at "tp_slots" then where tp_getattro is populated. And the fact that B.v
initially prints __get__, obj=None, objtype=<class '__main__.B'>
makes sense to me.
我不明白的是,为什么分配B.v = 3
盲目地覆盖描述符,而不是触发v.__set__
?我试图跟踪CPython调用,从"tp_slots" ,然后查看其中填充了tp_setattro ,然后查看 type_setattro 一个>. type_setattro
似乎是薄薄的包装纸 _PyObject_GenericSetAttrWithDict .我困惑的症结在于:_PyObject_GenericSetAttrWithDict
似乎具有优先使用描述符的__set__
方法的逻辑!考虑到这一点,我想不出为什么B.v = 3
盲目地覆盖v
而不是触发v.__set__
.
What I don't understand is, why does the assignment B.v = 3
blindly overwrite the descriptor, rather than triggering v.__set__
? I tried to trace the CPython call, starting once more from "tp_slots", then looking at where tp_setattro is populated, then looking at type_setattro. type_setattro
appears to be a thin wrapper around _PyObject_GenericSetAttrWithDict. And there's the crux of my confusion: _PyObject_GenericSetAttrWithDict
appears to have logic that gives precedence to a descriptor's __set__
method!! With this in mind, I can't figure out why B.v = 3
blindly overwrites v
rather than triggering v.__set__
.
免责声明1:我没有使用printfs从源代码重建Python,所以我没有完全确定type_setattro
是在B.v = 3
期间被调用的内容.
Disclaimer 1: I did not rebuild Python from source with printfs, so I'm notcompletely sure type_setattro
is what's being called during B.v = 3
.
免责声明2:VocalDescriptor
并非示例典型"或推荐"描述符定义.告诉我何时调用方法是一个冗长的操作.
Disclaimer 2: VocalDescriptor
is not intended to exemplify "typical" or "recommended" descriptor definition. It's a verbose no-op to tell me when the methods are being called.
推荐答案
您正确的认为B.v = 3
只是用整数覆盖描述符(应该如此).
You are correct that B.v = 3
simply overwrites the descriptor with an integer (as it should).
要使B.v = 3
调用描述符,应该在元类上(即type(B)
上)定义描述符.
For B.v = 3
to invoke a descriptor, the descriptor should have been defined on the metaclass, i.e. on type(B)
.
>>> class BMeta(type):
... v = VocalDescriptor()
...
>>> class B(metaclass=BMeta):
... pass
...
>>> B.v = 3
__set__
要在B
上调用描述符,您将使用一个实例:B().v = 3
将执行此操作.
To invoke the descriptor on B
, you would use an instance: B().v = 3
will do it.
B.v
调用getter的原因是允许返回描述符实例本身.通常,您会这样做,以允许通过类对象访问描述符:
The reason for B.v
invoking the getter is to allow returning the descriptor instance itself. Usually you would do that, to allow access on the descriptor via the class object:
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
的所有实例之间共享.
Now B.v
would return some instance like <mymodule.VocalDescriptor object at 0xdeadbeef>
which you can interact with. It is literally the descriptor object, defined as a class attribute, and its state B.v.__dict__
is shared between all instances of B
.
当然,完全由用户代码来定义他们想要B.v
要做的事情,返回self
只是常见的模式.
Of course it is up to user's code to define exactly what they want B.v
to do, returning self
is just the common pattern.
这篇关于为什么在类上设置描述符会覆盖描述符?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!