带有数据描述符的python属性访问

时间:2018-08-12 08:51:50

标签: python instance-variables

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

  1. 尝试访问当前类a和基类__dict __
  2. 中的数据描述符(名为__dict__
  3. a中找到obj.__dict__
  4. 在当前类a和基类__dict__中查找非数据描述符(命名为__dict__
  5. 在当前类a和基类__dict__中查找属性(名为__dict__
  6. 致电__getattr__(如果有)
  7. 提高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)将调用__get__中的ADesc,因为根据第一条规则,基类__dict__的{​​{1}}包含属性我们正在访问的A,因此应该调用数据描述符。那么上述访问规则是错误的还是这里涉及到其他魔术?

2 个答案:

答案 0 :(得分:2)

在类和基类中没有三个单独的搜索。 (此外,它不仅是类及其基础;还是整个MRO。)通过MRO进行一次搜索,一旦找到内容,它就会停止,无论所找到的对象可能在描述符协议的哪个部分,也可能不支持。

当对b.dd_1的搜索找到'dd_1 in B'时,它将停止MRO搜索。它并不会一直只是因为'dd_1 in B'不是描述符。

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

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

答案 1 :(得分:1)

您误解了如何在类中找到描述符。 Python将在类层次结构中使用 first 这样的名称。找到后,搜索将停止。 B.dd_1存在,因此不考虑A.dd_1

文档为您介绍了B未定义dd_1的情况下的基类;在这种情况下,将搜索B,然后搜索A。但是,当B具有属性dd_1时,将停止进一步的搜索。

请注意,搜索顺序由类MRO(方法解析顺序)设置。除了区分类__dict__中的搜索和基类的__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体现)包括当前的类对象:

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

相关文档位于datamodel reference中;其中自定义类指出:

  

将类属性引用转换为此字典中的查找,例如,C.x被转换为C.__dict__["x"](尽管有许多允许使用其他方法定位属性的钩子)。 在此处找不到属性名称时,将继续在基类中搜索属性。

实例属性的实际实现如下:

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