我偶然发现了双重下划线名称的这种行为,我不明白:
class A:
pass
class B:
pass
class C(A,B):
__id__ = 'c'
c = C()
print(C.__mro__) # print the method resolution order of class C
#print(c.__mro__) # AttributeError: 'C' object has no attribute '__mro__'
print(C.__id__) # print 'c'
print(c.__id__) # print 'c'
我知道__name
的名称修改,它不适用于__name__
(更多用于重载运算符方法)。 __id__
的行为就像一个普通的类变量,可以通过类名和实例来访问。
但是,__mro__
只能通过类名访问,实际上我甚至可以在C中明确地引入__mro__
:
class C(A,B):
__mro__ = 'bla'
print(C.__mro__) # print the method resolution order of class C
print(c.__mro__) # print 'bla'
我想了解这种行为是否是某种python内部魔法还是可以在常规python代码中实现。
[ python版本3.4.3 ]
答案 0 :(得分:4)
这与查找顺序有关。
让descriptors放在一边,python首先检查对象__dict__
以找到属性。如果找不到它,它将查看对象的类和类的基础以查找属性。如果在那里找不到它,则会引发AttributeError。
这可能是不可理解的,所以让我们用一个简短的例子来说明这一点:
#!/usr/bin/python3
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
)访问,但不能在baz
的实例Bar
上访问,因为它只在{__dict__
中1}}位于Foo
,不在__dict__
或Bar
的{{1}}。 Python只检查" meta"中的 one 。层次结构。
有关元类魔法的更多信息,请参阅What is a metaclass in python?问题的优秀答案。
但是,这不足以描述行为,因为baz
对于__mro__
的每个实例(即每个类)都不同。
这可以使用描述符来实现。 在之前查找对象Foo
的属性名称时,python会检查类的__dict__
及其基数,以查看是否有名称对象分配给描述符对象。描述符是具有__get__
method的任何对象。如果是这种情况,则调用描述符对象__dict__
方法,并从属性查找返回结果。通过将描述符分配给元类的属性,可以实现所看到的行为:描述符可以基于实例参数返回不同的值,但是该属性只能通过类和元类,而不是类的实例。
描述符的一个主要示例是property
。这是一个简单的示例,其描述符与__get__
具有相同的行为:
__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
可以为每个Z
实例设置不同的值,即每个类使用OtherFoo
元类。
元类最初会让人感到困惑,当描述符在播放时更是如此。我建议阅读linked question的元类,以及python中的描述符。