Унаследовать метаклассы от типа? - PullRequest
29 голосов
/ 27 января 2010

Я пытался понять метаклассы Python, и поэтому проходил через некоторый пример кода. Насколько я понимаю, метакласс Python может быть любым вызываемым. Итак, мой метакласс может иметь вид

def metacls(clsName, bases, atts):
    ....
    return type(clsName, bases, atts)

Однако я видел, как многие люди пишут свои метаклассы следующим образом:

class Metacls(type):
    def __new__(meta, clsName, bases, atts):
        ....
        return type.__new__(meta, clsName, bases, atts)

Насколько я понимаю, они оба будут делать одно и то же. Есть ли причина использовать базовый класс вместо этого? Это принято?

1 Ответ

39 голосов
/ 28 января 2010

Есть тонкие различия, в основном связанные с наследованием. При использовании функционировать как метакласс, результирующий класс действительно является экземпляром type, и могут быть унаследованы от без ограничений; однако функция метакласса никогда не будет вызван для таких подклассов. При использовании подкласса type в качестве метакласс, результирующий класс будет экземпляром этого метакласса, как будет любой из его подклассов; однако множественное наследование будет ограничено.

Иллюстрируя различия:

>>> def m1(name, bases, atts):
>>>     print "m1 called for " + name
>>>     return type(name, bases, atts)
>>>

>>> def m2(name, bases, atts):
>>>     print "m2 called for " + name
>>>     return type(name, bases, atts)
>>>

>>> class c1(object):
>>>     __metaclass__ = m1
m1 called for c1

>>> type(c1)
<type 'type'>

>>> class sub1(c1):
>>>     pass

>>> type(sub1)
<type 'type'>

>>> class c2(object):
>>>     __metaclass__ = m2
m2 called for c2

>>> class sub2(c1, c2):
>>>     pass

>>> type(sub2)
<type 'type'>

Обратите внимание, что при определении sub1 и sub2 функции метакласса не вызывались. Они будут созданы точно так же, как если бы c1 и c2 не имели метаклассов, но вместо этого манипулировали после создания.

>>> class M1(type):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M1 called for " + name
>>>         return super(M1, meta).__new__(meta, name, bases, atts)

>>> class C1(object):
>>>     __metaclass__ = M1
M1 called for C1

>>> type(C1)
<class '__main__.M1'>

>>> class Sub1(C1):
>>>     pass
M1 called for Sub1

>>> type(Sub1)
<class '__main__.M1'>

Обратите внимание на различия уже: M1 был вызван при создании Sub1, и оба классы являются экземплярами M1. Я использую super() для фактического создания здесь, по причинам, которые станут понятны позже.

>>> class M2(type):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M2 called for " + name
>>>         return super(M2, meta).__new__(meta, name, bases, atts)

>>> class C2(object):
>>>     __metaclass__ = M2
M2 called for C2

>>> type(C2)
<class '__main__.M2'>

>>> class Sub2(C1, C2):
>>>     pass
M1 called for Sub2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 23, in __new__
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Это основное ограничение множественного наследования с метаклассами. Python не знает, являются ли M1 и M2 совместимыми метаклассами, поэтому он заставляет вас создавать новый, чтобы гарантировать, что он делает то, что вам нужно.

>>> class M3(M1, M2):
>>>     def __new__(meta, name, bases, atts):
>>>         print "M3 called for " + name
>>>         return super(M3, meta).__new__(meta, name, bases, atts)

>>> class C3(C1, C2):
>>>     __metaclass__ = M3
M3 called for C3
M1 called for C3
M2 called for C3

>>> type(C3)
<class '__main__.M3'>

Вот почему я использовал super() в функциях метакласса __new__: так что каждая Можете позвонить следующему в MRO.

В некоторых случаях может потребоваться, чтобы ваши классы были типа type, или может потребоваться чтобы избежать проблем наследования, в этом случае функция метакласса, вероятно, путь В других случаях тип класса может быть действительно важным, или вы можете работать на всех подклассах, в этом случае подклассы type было бы лучшей идеей. Не стесняйтесь использовать стиль, который подходит лучше всего в в любой конкретной ситуации.

...