Чем __mro__ отличается от других двойных подчеркиваний?
Я наткнулся на это поведение для двойного подчеркивания имени, которое я не понимаю:
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__
можно получить доступ только через имя класса, и на самом деле я могу даже явно представить __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.
[версия питона 3.4.3]
1 ответ
Это связано с порядком поиска.
Оставляя дескрипторы в стороне, 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__
в Foo
не в __dict__
из Bar
или же baz
, Python проверяет только один шаг в иерархии "мета".
Для получения дополнительной информации о магии метакласса см. Превосходные ответы на вопрос " Что такое метакласс в python?".,
Этого, однако, недостаточно для описания поведения, потому что __mro__
отличается для каждого экземпляра Foo
(то есть для каждого класса).
Это может быть достигнуто с помощью дескрипторов. Прежде чем имя атрибута будет найдено на объектах __dict__
Питон проверяет __dict__
класса и его оснований, чтобы увидеть, есть ли объект дескриптора, назначенный на имя. Дескриптор - это любой объект, который имеет __get__
метод. Если это так, дескриптор объектов __get__
метод вызывается и результат возвращается из поиска атрибута. С помощью дескриптора, назначенного атрибуту метакласса, можно добиться наблюдаемого поведения: дескриптор может возвращать другое значение на основе аргумента экземпляра, но, тем не менее, доступ к атрибуту возможен только через класс и метакласс, а не экземпляры учебный класс.
Ярким примером дескрипторов является 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
не. Еще, Z
может иметь различное значение для каждого OtherFoo
экземпляр, то есть каждый класс, использующий OtherFoo
метаклассом.
Поначалу метаклассы сбивают с толку, и тем более, когда дескрипторы находятся в игре. Я предлагаю прочитать на метаклассах связанный вопрос, а также дескрипторы в python в целом.