__mro__ 与其他双下划线名称有何不同? [英] How is __mro__ different from other double underscore names?

查看:91
本文介绍了__mro__ 与其他双下划线名称有何不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我偶然发现了我不理解的双下划线名称的这种行为:

A 类:经过B类:经过C(A,B)类:__id__ = 'c'c = C()print(C.__mro__) # 打印C类的方法解析顺序#print(c.__mro__) # AttributeError: 'C' 对象没有属性 '__mro__'打印(C.__id__)#打印'c'打印(c.__id__)#打印'c'

我知道 __name 的名称修改,这不适用于 __name__(更多用于重载运算符方法).__id__ 的行为就像一个普通的类变量,可以通过类名和实例访问.

然而,__mro__ 只能通过类名访问,事实上我什至可以在 C 中显式地引入 __mro__:

C 类(A,B):__mro__ = 'bla'print(C.__mro__) # 打印C类的方法解析顺序打印(c.__mro__)#打印'bla'

我想了解这种行为是否是 Python 内部的某种魔法,还是可以在常规 Python 代码中实现.

[python 3.4.3 版]

解决方案

这与查找顺序有关.

撇开描述符不谈,python首先检查对象__dict__ 来查找属性.如果找不到,它将查看对象的类和类的基类以查找属性.如果在那里也找不到,则引发 AttributeError.

这可能无法理解,所以让我们用一个简短的例子来说明这一点:

#!/usr/bin/python3类 Foo(类型):X = 10类酒吧(元类= Foo):Y = 20巴兹 = 酒吧()打印(X on Foo",hasattr(Foo,X"))打印(X on Bar",hasattr(Bar,X"))打印(X on baz",hasattr(baz,X"))print("Y on Foo", hasattr(Foo, "Y"))打印(Y on Bar",hasattr(Bar,Y"))打印(Y on baz",hasattr(baz,Y"))

输出为:

X on Foo TrueX on Bar TrueX on baz 错误Y on Foo FalseY on Bar TrueY on baz True

如您所见,X 已在 metaclass Foo 上声明.它可以通过 metaclass 的实例,Bar 类访问,但不能在 Bar 的实例 baz 上访问,因为它只在Foo中的__dict__中,而不在Barbaz的__dict__.Python 只检查元"层次结构中的一个步骤.

有关元类魔法的更多信息,请参阅问题python中的元类是什么?的优秀答案.>

然而,这不足以描述行为,因为 __mro__ 对于 Foo 的每个 instance 都是不同的(也就是说,对于每个班级).

这可以使用描述符来实现.在对象__dict__中查找属性名称之前,python会检查类的__dict__及其基类是否有描述符分配给名称的对象.描述符是具有 __get__ 方法的任何对象.如果是这种情况,将调用描述符对象的 __get__ 方法并从属性查找中返回结果.使用分配给元类属性的描述符,可以实现所见行为:描述符可以根据 instance 参数返回不同的值,但是该属性只能通过类访问元类,而不是类的实例.

描述符的一个主要例子是property.这是一个简单的例子,其描述符与 __mro__ 具有相同的行为:

类描述符:def __get__(self, instance, owner):返回基于 {} 的一些值".format(instance)类其他Foo(类型):Z = 描述符()类 OtherBar(metaclass=OtherFoo):经过other_baz = OtherBar()print("Z on OtherFoo", hasattr(OtherFoo, "Z"))print("Z on OtherBar", hasattr(OtherBar, "Z"))打印(Z on other_baz",hasattr(other_baz,Z"))print("OtherFoo 上 Z 的值", OtherFoo.Z)print("OtherBar 上 Z 的值", OtherBar.Z)

输出为:

Z on OtherFoo TrueZ on OtherBar TrueZ on other_baz 假OtherFoo 上 Z 的值一些基于 None 的值OtherBar 上 Z 的值一些基于  的值

如您所见,OtherBarOtherFoo 都具有可访问的 Z 属性,但 other_baz 没有.尽管如此,对于每个 OtherFoo 实例,Z 可以有不同的值,也就是说,每个使用 OtherFoo 元类的类.

元类起初令人困惑,在使用描述符时更是如此.我建议阅读元类的链接问题,以及一般的python中的描述符.

I stumbled upon this behavior for double underscore name that I don't understand:

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'

I know about the name mangling for __name, which doesn't apply for __name__ (more for overloading operator methods). __id__ behaves just like a regular class variable which can be accessed via Class name as well as instance.

However, __mro__ can only be accessed via Class name and in fact I can even explicitly introduce __mro__ in C:

class C(A,B):
    __mro__ = 'bla'

print(C.__mro__) # print the method resolution order of class C
print(c.__mro__) # print 'bla'

I'd like to understand if this behavior is some python internal magic or can it be achieved in regular python code.

[python version 3.4.3]

解决方案

This has to do with the lookup order.

Letting descriptors aside, python first checks the objects __dict__ to find an attribute. If it cannot find it, it will look at the class of the object and the bases of the class to find the attribute. If it cannot be found there either, AttributeError is raised.

This is probably not understandable, so let us show this with a short example:

#!/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")) 

The output is:

X on Foo True
X on Bar True
X on baz False
Y on Foo False
Y on Bar True
Y on baz True

As you can see, X has been declared on the metaclass Foo. It is accessible through the instance of the metaclass, the class Bar, but not on the instance baz of Bar, because it is only in the __dict__ in Foo, not in the __dict__ of Bar or baz. Python only checks one step up in the "meta" hierarchy.

For more on metaclass magic, see the excellent answers on the question What is a metaclass in python?.

This, however, is not sufficient to describe the behaviour, because __mro__ is different for each instance of Foo (that is, for each class).

This can be achieved using descriptors. Before the attribute name is looked up at the objects __dict__, python checks the __dict__ of the class and its bases to see if there is a descriptor object assigned to the name. A descriptor is any object which has a __get__ method. If that is the case, the descriptor objects __get__ method is called and the result is returned from the attribute lookup. With a descriptor assigned to an attribute of the metaclass, the behaviour seen can be achieved: The descriptor can return a different value based on the instance argument, but nevertheless the attribute can only be accessed through the class and the metaclass, not instances of the class.

A prime example of descriptors is property. Here is a simple example with a descriptor which has the same behaviour as __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)

The output is:

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'>

As you can see, OtherBar and OtherFoo both have the Z attribute accessible, but other_baz does not. Still, Z can have a different value for each OtherFoo instance, that is, each class using the OtherFoo metaclass.

Metaclasses are confusing at first, and even more so when descriptors are in play. I suggest reading up on metaclasses the linked question, as well as descriptors in python in general.

这篇关于__mro__ 与其他双下划线名称有何不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆