2016-07-29 1 views
4

Я наткнулся на это поведение для двойного имени подчеркивания, что я не понимаю:Как __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__ в C:

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

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

Я хотел бы понять, если это поведение некоторых питон внутренней магии или это может быть достигнуто в правильном коде python.

[питона версии 3.4.3]

ответ

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

+2

Последний выходной блок выглядит так, как будто вы скопировали неверный блок. :) – acdr

+0

@acdr Исправлено, спасибо! –

+0

@ Джонас, не знал о метаклассе, поэтому потребовалось время, чтобы понять ваш ответ. Чтобы подвести итог, '__mro__' - это атрибут, определенный в метаклассе, доступ к которому возможен только через класс, являющийся экземпляром указанного метакласса. Кроме того, он определяется как дескриптор, поэтому имеет приоритет над '__mro__', явно определенным в самом классе. Верный? –

Смежные вопросы