Это связано с порядком поиска.
Взяв 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__
в Foo
, а не в __dict__
из Bar
или baz
. Python только проверяет один шаг в иерархии «meta».
Подробнее о метаклассовом магии см. Превосходные ответы на вопрос What is a metaclass in python?.
Это, однако, не является достаточным, чтобы описать поведение, потому что __mro__
отличается для каждого экземпляра из Foo
(то есть, для каждого класса).
Этого можно достичь с помощью дескрипторов. До имя атрибута отображается на объектах __dict__
, python проверяет класс и его базы __dict__
, чтобы узнать, существует ли объект-дескриптор, назначенный этому имени. Дескриптором является любой объект, который имеет __get__
method. Если это так, вызываются объекты дескриптора __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
.
Метаклассы сбивают с толку сначала, а тем более, когда дескрипторы находятся в игре. Я предлагаю читать метаклассы linked question, а также дескрипторы в python в целом.
Последний выходной блок выглядит так, как будто вы скопировали неверный блок. :) – acdr
@acdr Исправлено, спасибо! –
@ Джонас, не знал о метаклассе, поэтому потребовалось время, чтобы понять ваш ответ. Чтобы подвести итог, '__mro__' - это атрибут, определенный в метаклассе, доступ к которому возможен только через класс, являющийся экземпляром указанного метакласса. Кроме того, он определяется как дескриптор, поэтому имеет приоритет над '__mro__', явно определенным в самом классе. Верный? –