2016-05-24 8 views
6

В этом хорошо известном answer, который объясняет метакласс в Python. Он упоминает, что атрибут __metaclass__ не будет унаследован.Наследование метакласса

Но на самом деле, я пытался в Python:

class Meta1(type): 
    def __new__(cls, clsname, bases, dct): 
     print "Using Meta1" 
     return type.__new__(cls, clsname, bases, dct) 

# "Using Meta1" printed 
class Foo1: 
    __metaclass__ = Meta1 

# "Using Meta1" printed 
class Bar1(Foo1): 
    pass 

Как и следовало ожидать, как Foo и Bar использование Meta1 как метакласс и печатаемой строка, как и ожидался. не

Но в следующем примере, когда type(...) возвращается вместо type.__new__(...), метаклассом больше не наследуется:

class Meta2(type): 
    def __new__(cls, clsname, bases, dct): 
     print "Using Meta2" 
     return type(clsname, bases, dct) 

# "Using Meta2" printed 
class Foo2: 
    __metaclass__ = Meta2 

# Nothing printed 
class Bar2(Foo2): 
    pass 

Проверка атрибутов __metaclass__ и __class__, я вижу:

print Foo1.__metaclass__ # <class '__main__.Meta1'> 
print Bar1.__metaclass__ # <class '__main__.Meta1'> 
print Foo2.__metaclass__ # <class '__main__.Meta2'> 
print Bar2.__metaclass__ # <class '__main__.Meta2'> 

print Foo1.__class__ # <class '__main__.Meta1'> 
print Bar1.__class__ # <class '__main__.Meta1'> 
print Foo2.__class__ # <type 'type'> 
print Bar2.__class__ # <type 'type'> 

В заключение:

  1. Оба __metaclass__ и __class__будут унаследованы от базового класса.

  2. Поведение создания определяется Meta2 будет использоваться для Foo2, хотя на самом деле Foo2.__class__type.

  3. Атрибут __metaclass__ в Bar2 является Meta2, но поведение создание Bar2 не влияет. В другом слове Bar2 использует type как «настоящий» метакласс вместо Meta2.

Эти наблюдения делают механизм наследования __metaclass__ рода расплывчатым мне.

Я думаю, что:

  1. При непосредственном назначении класса (например, Meta1) с атрибутом другого класса «Foo1» __metaclass__, Это __metaclass__ атрибут принимает эффект.

  2. Когда подкласс явно не задает атрибут __metaclass__ при определении. Атрибут __class__ вместо атрибута __metaclass__ базового класса будет определять «реальный» метакласс подкласса.

Является ли мое предположение правильным? Как Python справляется с наследованием метакласса?

+0

Я думаю, что это связано с типом .__ new__ и type http://stackoverflow.com/questions/2608708/what-is-the-difference-between-type-and -типа новая-в-питон –

ответ

3

Вы спекулируете много, в то время как минималистский и «Специальный случаи» Python не являются достаточно специальными, чтобы нарушать правила ». директиву, легче понять, чем это.

В Python2 атрибут __metaclass__ в кубе класса используется во время создания класса, чтобы вызвать класс, который будет принадлежать этому классу. Обычно это класс с именем type.Чтобы прояснить этот момент, после того, как парсер проанализировал тело класса, после компилятора он скомпилировал его в объект кода и после того, как он был фактически запущен во время выполнения программы, и только если в этом классе явно указано __metaclass__.

Так способ проверить, что позволяет разрешить идет в таком случае, как:

class A(object): 
    __metaclass__ = MetaA 

class B(A): 
    pass 

A имеет в своем теле __metaclass__ - MetaA вызывается вместо type, чтобы превратить его в «объект класса». B не имеет __metaclass__ в своем теле. После его создания, если вы просто попытаетесь получить доступ к атрибуту __metaclass__, это атрибут как любой другой, который будет виден, потому что Python выдает его из суперкласса A. Если вы проверите A.__dict__, вы увидите __metaclass__, и если вы проверите B.__dict__, не делайте этого.

Этот атрибут A.__metaclass__ является не используется вообще, когда B создан. Если вы измените его в A до объявления B, он по-прежнему будет использовать тот же метакласс, что и A, потому что Python использует тип родительского класса в качестве метакласса в отсутствии объявления явного __metaclass__.

Для иллюстрации:

In [1]: class M(type): pass 

In [2]: class A(object): __metaclass__ = M 

In [3]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(A.__class__, A.__metaclass__, A.__dict__.get("__metaclass__"), type(A)) 
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: <class '__main__.M'>, type: <class '__main__.M'> 

In [4]: class B(A): pass 

In [5]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(B.__class__, B.__metaclass__, B.__dict__.get("__metaclass__"), type(B)) 
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: None, type: <class '__main__.M'> 

In [6]: A.__metaclass__ = type 

In [8]: class C(A): pass 

In [9]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(C.__class__, C.__metaclass__, C.__dict__.get("__metaclass__"), type(C)) 
class: <class '__main__.M'>, metaclass_attr: <type 'type'>, metaclass_in_dict: None, type: <class '__main__.M'> 

Кроме того, если вы пытаетесь просто создать класс с помощью вызова type вместо того, чтобы использовать тело с class заявлением, __metaclass__ также просто обычный атрибут:

In [11]: D = type("D", (object,), {"__metaclass__": M}) 

In [12]: type(D) 
type 

Подведение итогов: Атрибут __metaclass__ в Python 2 является особым, если он явно помещен в тело класса объявление как часть выполнения инструкции блока class. Это обычный атрибут без каких-либо специальных свойств.

Python3 оба избавились от этого странного «атрибута __metaclass__», и теперь для дальнейшей настройки тела класса разрешено изменять синтаксис, чтобы указать метаклассы. (Это как объявлено, как если бы он был «metaclass именованный параметр» на самом class заявление)

Теперь ко второй части того, что подняли свои сомнения: если в __new__ методе метакласса вы называете type вместо type.__new__, нет возможности, что Python может «знать» type вызывается из производного метакласса. Когда вы вызываете type.__new__, вы передаете в качестве своего первого параметра атрибут cls, который сам metaclass __new__ был передан средой выполнения: это означает, что результирующий класс является экземпляром подкласса type.Это так же, как наследование работает для любого другого класса в Python - так «никакого специальное поведения» здесь:

Таким образом, заметить разницу:

class M1(type): 
    def __new__(metacls, name, bases, attrs): 
     cls = type.__new__(metacls, name, bases, attrs) 
     # cls now is an instance of "M1" 
     ... 
     return cls 


class M2(type): 
    def __new__(metacls, name, bases, attrs): 
     cls = type(cls, name, bases, attrs) 
     # Type does not "know" it was called from within "M2" 
     # cls is an ordinary instance of "type" 
     ... 
     return cls 

Это можно увидеть в интерактивном режиме:

In [13]: class M2(type): 
    ....:  def __new__(metacls, name, bases, attrs): 
    ....:   return type(name, bases, attrs) 
    ....:  

In [14]: class A(M2): pass 

In [15]: type(A) 
Out[15]: type 

In [16]: class A(M2): __metaclass__ = M2 

In [17]: A.__class__, A.__metaclass__ 
Out[17]: (type, __main__.M2) 

(Обратите внимание, что метаклассом __new__ метод первый параметр является сам метаклассом, поэтому более правильно назвали metacls чем cls как в вашем коде, и я na много кода «в дикой природе»)