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

2024-09-30 20:22:15 发布

您现在位置:Python中文网/ 问答频道 /正文

简单复制:

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的帮助。为了我自己和未来读者的利益,我在追踪我正在看的电话时尽量明确。在

我看到很多墨水溢出到了应用于描述符的__getattribute__行为上,例如查找优先级。在For classes, the machinery is in type.__getattribute__()...下面的"Invoking Descriptors"中的Python片段大致与我认为的type_getattro中的CPython source大致一致,我通过查看"tp_slots"然后where tp_getattro is populated找到了它。事实上B.v最初打印{}对我来说是有意义的。在

我不明白的是,为什么赋值B.v = 3盲目重写描述符,而不是触发{}?我试图跟踪CPython调用,从"tp_slots"开始,然后查看where tp_setattro is populated,然后查看{a9}。type_setattroappears to be一个围绕_PyObject_GenericSetAttrWithDict的薄包装。我困惑的症结是:_PyObject_GenericSetAttrWithDict似乎有{a12}!!考虑到这一点,我不明白为什么B.v = 3盲目地重写{},而不是触发{}。在

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

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


Tags: objgetobjectistypenotcpython描述符
2条回答

除非有任何重写,B.v等同于type.__getattribute__(B, "v"),而{}则等价于object.__getattribute__(b, "v")。如果定义了结果,这两个定义都会调用结果的__get__方法。在

请注意,对__get__的调用在每种情况下都是不同的。B.v传递None作为第一个参数,而{}传递实例本身。在这两种情况下,B都作为第二个参数传递。在

另一方面,B.v = 3相当于type.__setattr__(B, "v", 3),它不调用__set__。在

B.v = 3只是用一个整数覆盖描述符(应该如此),这是正确的。在

为了使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的原因是允许返回描述符实例本身。通常您会这样做,以允许通过类对象访问描述符:

^{pr2}$

现在,B.v将返回一些实例,如<mymodule.VocalDescriptor object at 0xdeadbeef>,您可以与之交互。它实际上是描述符对象,定义为类属性,其状态B.v.__dict__在{}的所有实例之间共享。在

当然,这取决于用户的代码来定义他们想要B.v做什么,返回{}只是一种常见模式。在

相关问题 更多 >