__mro__与其他双下划线名称有什么不同?

时间:2016-07-29 09:56:37

标签: python python-3.4 double-underscore

我偶然发现了双重下划线名称的这种行为,我不明白:

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 ]

1 个答案:

答案 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中的描述符。