Чем __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 в целом.

Другие вопросы по тегам