这与查找顺序有关。
不考虑描述符,Python首先检查对象的__dict__
以查找属性。如果找不到,则会查找对象的类和类的基类以查找属性。如果还找不到,则会引发AttributeError。
这可能不容易理解,让我们通过一个简短的例子来说明:
class Foo(type):
X = 10
class Bar(metaclass=Foo):
Y = 20
baz = Bar()
print("X on Foo", hasattr(Foo, "X"))
print("X on Bar", hasattr(Bar, "X"))
print("X on baz", hasattr(baz, "X"))
print("Y on Foo", hasattr(Foo, "Y"))
print("Y on Bar", hasattr(Bar, "Y"))
print("Y on baz", hasattr(baz, "Y"))
输出结果为:
X on Foo True
X on Bar True
X on baz False
Y on Foo False
Y on Bar True
Y on baz True
如您所见,
X
已在元类
Foo
上声明。它可以通过元类的实例(类
Bar
)访问,但不能通过
Bar
或
baz
的
__dict__
访问,因为它只存在于
Foo
的
__dict__
中,而不是
Bar
或
baz
的
__dict__
中。Python仅在“元”层次结构中检查一步。
有关元类魔法的更多信息,请参见问题
“Python中的元类是什么?”上的优秀答案。
然而,这并不足以描述行为,因为每个
Foo
实例(即每个类)的
__mro__
都不同。
这可以使用描述符实现。在将属性名称查找对象的
__dict__
之前,Python会检查类及其基类的
__dict__
,以查看是否将描述符对象分配给名称。描述符是任何具有
__get__
方法的对象。如果是这种情况,则调用描述符对象的
__get__
方法,并从属性查找返回结果。通过将描述符分配给元类的属性,可以实现所见行为:描述符可以基于
instance参数返回不同的值,但是仍然只能通过类和元类访问该属性,而不是类的实例。
描述符的一个主要示例是
property
。这里是一个简单的示例,其中具有与
__mro__
相同的行为的描述符:
class Descriptor:
def __get__(self, instance, owner):
return "some value based on {}".format(instance)
class OtherFoo(type):
Z = Descriptor()
class OtherBar(metaclass=OtherFoo):
pass
other_baz = OtherBar()
print("Z on OtherFoo", hasattr(OtherFoo, "Z"))
print("Z on OtherBar", hasattr(OtherBar, "Z"))
print("Z on other_baz", hasattr(other_baz, "Z"))
print("value of Z on OtherFoo", OtherFoo.Z)
print("value of Z on OtherBar", OtherBar.Z)
输出结果如下:
Z on OtherFoo True
Z on OtherBar True
Z on other_baz False
value of Z on OtherFoo some value based on None
value of Z on OtherBar some value based on <class '__main__.OtherBar'>
如您所见,
OtherBar
和
OtherFoo
都可以访问
Z
属性,但
other_baz
不能。不过每个
OtherFoo
实例仍可以为
Z
属性赋不同的值,也就是每个使用
OtherFoo
元类的类。
元类一开始可能比较难理解,当涉及到描述符时更加复杂。建议阅读关于元类的相关问题以及Python中描述符的相关知识。
__mro__
是一个在元类上定义的属性,只能通过该元类的实例类访问。此外,它被定义为一个描述符,因此优先于在类本身上显式定义的__mro__
属性。对吗? - Kevin S.cls.__mro__ == type(cls).__dict__['__mro__'].__get__(cls)
。 - Solomon Ucko