关于数据型描述器(data descriptor)与实例属性的优先级问题

这里的官方文档描述了在形如a.b的过程中都发生了什么:

首先会尝试a.__dict__['b'],然后如果没找到,则type(a).__dict__['b'],再没找到则顺着mro顺序逐个尝试,直到找到未知。且在找到之后查看这里的b是否定义有描述器相关的方法__get__ / __set__,如果有的话,则用描述器相关方法来取代默认行为。

有下面这样一个简单例子:

class Descri(object):
    def __get__(self, obj, type=None):
        print("call get")
    
    def __set__(self, obj, value):
        print("call set")

class A(object):
    x = Descri()

a = A()
a.__dict__['x'] = 1

在最后一行执行完毕之后,我们获取a.x
按照前面说的查找顺序,应该直接在实例a__dict__中就能够找到x属性,且其等于1并且没有定义任何的特殊方法,那么按照查找优先级来看应该直接返回1,而且不应该继续查找任何的type(a).__dict__才对。但是实际却不是这样,实际上依然调用了Descri.__get__方法。
请问这是为什么?

阅读 2.1k
2 个回答

谢邀,a.x 在实例属性的变量空间就命中了,因此不会到上级的类的变量空间再查找了。在我这的测试,也并没有调用 Descri.__get__:

图片描述

本地的 3.6 版本也是一样的结果。

======分割线=====
根据题主说的在最后一行试试 a.x ,还真是执行了 Descri.__get__ 。这种诡异的调用顺序其实是在文档里就有的解释的。

一个属性的访问已被描述符协议方法重写对象属性: __get__()__set__(),和 __delete__()。如果为对象定义了任何这些方法,则称其为描述符。

一般情况下(我是说一般情况下),a.x 的查找链是,先 a.__dict__['x'] 然后再是 type(a).__dict__['x'] ,若没找到则继续通过 type(a) 的基类查找。

但是但是,如果查找的值其中一个是定义了描述符方法的对象,那么 Python 会覆盖这个默认行为转而调用描述符的方法。这种行为也会因为调用的不同而稍有不一样:

  • 如果调用是对象实例(题目中的调用方式),a.x 则转换为调用: 。type(a).__dict__['x'].__get__(a, type(a))
  • 如果调用的是类属性, A.x 则转换为:A.__dict__['x'].__get__(None, A)
  • 其他情况

更多参考可见文档:https://docs.python.org/3/ref...

新手上路,请多包涵

先说下描述符的分类:

  1. 数据描述符
    至少实现了__set__和__get__
  2. 非数据描述符
    只实现了__get__

再说下描述符的访问优先级:
类.属性 > 数据描述符 > 对象.属性 > 非数据描述符

当你用a.__dict__['x'] = 1,会向a的名称空间写入{'x': 1}。但是当你用a.x访问时,由于你实现的是数据描述符,优先级高于对象.属性,此时会执行type(a).__dict__['x'].__get__(a,type(a)),而不是a.__dict__['x']。

如果你希望执行a.x时,返回a.__dict__['x']。把上面的__set__注释掉就好了。因为此时实现的是非数据描述法,优先级小于对象.属性。
希望对你有帮助!

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题