对象类的描述符__class的__get__没有按预期返回

And*_* L. 8 python python-3.x

我尝试更彻底地了解描述符和显式属性名称的查找顺序。
我阅读了描述符howto,其状态如下:

调用的细节取决于obj是对象还是类:
...
对于类,机制在type.__getattribute__()中,它转换B.xB.__dict__['x'].__get__(None, B)

我在__class__ 上进行了测试,因为它是的数据描述符object

In [47]: object.__class__
Out[47]: type   
Run Code Online (Sandbox Code Playgroud)

因此,type由于typeclass创建了包括objectclass 在内的所有类,因此它按预期返回。根据“描述符howto”,object.__class__将其转换为object.__dict__['__class__'].__get__(None, object)
但是,当我运行它时,输出是描述符本身,而不是描述符type

In [48]: object.__dict__['__class__'].__get__(None, object)
Out[48]: <attribute '__class__' of 'object' objects>    
Run Code Online (Sandbox Code Playgroud)

我猜它返回描述符本身,因为在__get__其中包含某种代码,例如:

if instance is None:
    return self    
Run Code Online (Sandbox Code Playgroud)

因此,我了解从类调用时返回描述符本身的原因。让我感到困惑的是不同的输出

当它说“ B.xB.__dict__['x'].__get__(None, B)”时,我期望输出是相同的。他们为什么不同?

use*_*ica 4

描述符操作方法是一种简化。它掩盖了元类之类的东西,以及类是对象的事实。类是对象,它们经历“对象样式”“类样式”属性查找和描述符处理。type_getattro(如果您想独立验证这一点,可以在 中找到实现。)

查找object.__class__不只是遍历object.__mro__;它也看透了type(object).__mro__type(object).__mro__在使用“对象样式”描述符处理中找到的描述符,将类视为其元类的实例,而在object.__mro__使用“类样式”描述符处理中找到的描述符。

当您查找时object.__class__,Python 会进行搜索type(object).__mro__。既然object是在type(object).__mro__,这一查就找到了object.__dict__['__class__']。由于object.__dict__['__class__']是一个数据描述符(它有一个__set__),因此它优先于通过 进行搜索object.__mro__。因此,Python 将其视为object实例object而不是类来执行

descr.__get__(object, type(object))
Run Code Online (Sandbox Code Playgroud)

代替

descr.__get__(None, object)
Run Code Online (Sandbox Code Playgroud)

调用__get__返回type(object),即type

您的手动descr.__get__(None, object)调用将被视为object一个类而不是 的实例object。通过这种方式调用,描述符返回自身。


为了证明__class__这里没有特殊情况,我们可以创建自己类,它是其自身的实例,就像object

class DummyMeta(type):
    pass

class SelfMeta(type, metaclass=DummyMeta):
    @property
    def x(self):
        return 3

SelfMeta.__class__ = SelfMeta

print(SelfMeta.x)
print(SelfMeta.__dict__['x'].__get__(None, SelfMeta))
print(SelfMeta.__dict__['x'].__get__(SelfMeta, type(SelfMeta)))
Run Code Online (Sandbox Code Playgroud)

输出:

3
<property object at 0x2aff9f04c5e8>
3
Run Code Online (Sandbox Code Playgroud)

就像 一样object.__class__,“对象样式”描述符处理也发生在这里。(另外,如果您想知道,即使您不编写设置器,属性也是数据描述符。)