python 使用数据描述符访问属性
python attribute access with data descriptor
我读过一些博客和文档,在访问实例属性时 obj.a
:
- 尝试访问当前 class
__dict__
和基础 class __dict__[=45= 中的数据描述符(名为 a
) ]
- 在
obj.__dict__
中找到a
- 在当前 class
__dict__
和基础 class __dict__
中找到非数据描述符(名为 a
)
- 在当前 class
__dict__
和基础 class __dict__
中查找属性(名为 a
)
- 如有请致电
__getattr__
- 提高
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__
。)
- 在对象的 MRO 中搜索与属性名称匹配的字典条目。
- 如果 MRO 搜索找到具有
__get__
方法的数据描述符,则停止搜索并使用该描述符。
- 否则,我们检查实例字典。如果有与属性名称匹配的条目,请停止并使用它。如果没有匹配的条目(或者如果没有实例字典),继续。
- 如果第 2 步搜索找到非数据描述符或非描述符,请使用它。
- 否则查找失败。引发 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
。
我读过一些博客和文档,在访问实例属性时 obj.a
:
- 尝试访问当前 class
__dict__
和基础 class __dict__[=45= 中的数据描述符(名为a
) ] - 在
obj.__dict__
中找到 - 在当前 class
__dict__
和基础 class__dict__
中找到非数据描述符(名为 - 在当前 class
__dict__
和基础 class__dict__
中查找属性(名为 - 如有请致电
__getattr__
- 提高
AttributeError
a
a
)
a
)
但是我发现这个搜索规则不符合下面代码的行为:
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__
。)
- 在对象的 MRO 中搜索与属性名称匹配的字典条目。
- 如果 MRO 搜索找到具有
__get__
方法的数据描述符,则停止搜索并使用该描述符。 - 否则,我们检查实例字典。如果有与属性名称匹配的条目,请停止并使用它。如果没有匹配的条目(或者如果没有实例字典),继续。
- 如果第 2 步搜索找到非数据描述符或非描述符,请使用它。
- 否则查找失败。引发 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 toC.__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
。