Mic*_*lli 10 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
Run Code Online (Sandbox Code Playgroud)
这个问题有一个有效的重复项,但是没有回答重复项,作为学习练习,我对CPython源码进行了更多研究。警告:我进入了杂草。我真希望我能从知道这些水域的船长那里得到帮助。为了我自己的未来利益和未来读者的利益,我试图尽可能明确地追踪正在寻找的电话。
我已经看到很多墨水溅到了__getattribute__应用于描述符的行为上,例如查找优先级。Python的片断在“援引描述符”下方For classes, the machinery is in type.__getattribute__()...大致在我的脑海里同意我认为是相应的CPython的源中type_getattro,我找到了通过看“tp_slots”然后tp_getattro填充其中。B.v最初打印的事实__get__, obj=None, objtype=<class '__main__.B'>对我来说很有意义。
我不明白的是,为什么分配会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__。
免责声明1:我没有使用printfs从源代码重建Python,所以我不确定type_setattro到底是在调用什么B.v = 3。
免责声明2: VocalDescriptor无意举例说明“典型”或“推荐”描述符定义。告诉我何时调用方法是一个冗长的操作。
正确的是,B.v = 3用一个整数覆盖描述符是正确的(应该这样做)。
为了B.v = 3调用描述符,应该在元类上(即在上)定义描述符type(B)。
>>> class BMeta(type):
... v = VocalDescriptor()
...
>>> class B(metaclass=BMeta):
... pass
...
>>> B.v = 3
__set__
Run Code Online (Sandbox Code Playgroud)
要在上调用描述符B,您可以使用一个实例:B().v = 3将其执行。
B.v调用getter 的原因是允许返回描述符实例本身。通常,您会这样做,以允许通过类对象访问描述符:
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__')
Run Code Online (Sandbox Code Playgroud)
现在B.v将返回一些<mymodule.VocalDescriptor object at 0xdeadbeef>您可以与之交互的实例。它实际上是描述符对象,定义为类属性,并且其状态B.v.__dict__在的所有实例之间共享B。
当然,完全由用户代码定义他们想要B.v做什么,返回self只是常见的模式。