python 使用数据描述符访问属性

python attribute access with data descriptor

我读过一些博客和文档,在访问实例属性时 obj.a:

  1. 尝试访问当前 class __dict__ 和基础 class __dict__[=45= 中的数据描述符(名为 a) ]
  2. obj.__dict__
  3. 中找到a
  4. 在当前 class __dict__ 和基础 class __dict__
  5. 中找到非数据描述符(名为 a
  6. 在当前 class __dict__ 和基础 class __dict__
  7. 中查找属性(名为 a
  8. 如有请致电__getattr__
  9. 提高AttributeError

但是我发现这个搜索规则不符合下面代码的行为:

class ADesc(object):
    def __init__(self, name):
        self._name = name

    def __get__(self, obj, objclass):
        print('get.....')
        return self._name + '  ' + str(obj) + '  ' + str(objclass)

    def __set__(self, obj, value):
        print('set.....')
        self._name = value


class A(object):
    dd_1 = ADesc('dd_1 in A')


class B(A):
    dd_1 = 'dd_1 in B'


if __name__ == '__main__':
    print(A.__dict__)
    # {'dd_1': <__main__.ADesc object at 0x10ed0d050>, '__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}

    print(B.__dict__)
    # {'dd_1': 'dd_1 in B', '__module__': '__main__', '__doc__': None}

    b = B()
    print(b.dd_1)  # dd_1 in B

我认为最后一个 print(b.dd_1) 将调用 ADesc 中的 __get__,因为根据第一条规则,基础 class 的 __dict__ A 包含我们正在访问的属性 dd_1,因此应该调用该数据描述符。那么上面的访问规则是错误的还是这里涉及的任何其他魔法?

class 和基础 class 没有三个单独的搜索。 (此外,它不仅仅是 class 及其基础;它是整个 MRO。)通过 MRO 进行一次搜索,一旦找到某些东西,它就会停止,而不管找到的对象是描述符协议的哪一部分可能支持也可能不支持。

当对 b.dd_1 的搜索找到 'dd_1 in B' 时,它会停止 MRO 搜索。它不会仅仅因为 'dd_1 in B' 不是描述符而继续查找。

这是在 object.__getattribute__ 中实现的标准属性解析逻辑的正确版本。 (这只是 object.__getattribute__;它不包括 class 和它们自己的 __getattribute____getattr__。)

  1. 在对象的 MRO 中搜索与属性名称匹配的字典条目。
  2. 如果 MRO 搜索找到具有 __get__ 方法的数据描述符,则停止搜索并使用该描述符。
  3. 否则,我们检查实例字典。如果有与属性名称匹配的条目,请停止并使用它。如果没有匹配的条目(或者如果没有实例字典),继续。
  4. 如果第 2 步搜索找到非数据描述符或非描述符,请使用它。
  5. 否则查找失败。引发 AttributeError。

您误解了如何在 classes 中找到描述符。 Python 将在 class 层次结构中使用 第一个 这样的名称。一旦找到,搜索就会停止。 B.dd_1存在,所以不考虑A.dd_1

文档告诉您关于 classes 的情况,用于 B 未定义 dd_1 的情况;在这种情况下,搜索 B,然后搜索 A。但是当 B 具有属性 dd_1 时,将停止任何进一步的搜索。

请注意,搜索顺序由 class MRO(方法解析顺序)设置。不要区分 class __dict__ 中的搜索和单独的 class 基本 __dict__ 属性,您应该将搜索视为:

def find_class_attribute(cls, name):
    for c in cls.__mro__:
        if name in c.__dict__:
            return c.__dict__[name]

MRO(由 cls.__mro__ attribute 体现)包括当前 class 对象:

>>> B.__mro__()
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

相关文档可在datamodel reference中找到;其中 Custom 类 指出:

Class attribute references are translated to lookups in this dictionary, e.g., C.x is translated to C.__dict__["x"] (although there are a number of hooks which allow for other means of locating attributes). When the attribute name is not found there, the attribute search continues in the base classes.

实例属性的实际实现是这样的:

  • class 位于 (type(instance))
  • 调用class__getattribute__方法(type(instance).__getattribute__(instance, name))
  • __getattribute__ 扫描 MRO 以查看名称是否存在于 class 及其基础 classes (find_class_attribute(self, name))
    • 如果存在这样的对象,并且是数据描述符(有__set____delete__方法),则使用该对象,搜索停止。
    • 如果有这样的对象但它不是数据描述符,则保留一个引用供以后使用。
  • __getattribute__instance.__dict__ 中查找名字
    • 如果有这样的对象,则停止搜索。使用实例属性。
  • 没有找到数据描述符,实例字典中也没有属性。但是通过 MRO 的搜索可能找到了一个非数据描述符对象
    • 如果存在对在 MRO 中找到的对象的引用,则会使用它并停止搜索。
  • 如果在 class(或基 class)上定义了 __getattr__ 方法,则调用它,并使用结果。搜索停止。
  • 引发了AttributeError