TypeErrors с использованием метаклассов в сочетании с множественным наследованием

У меня есть два вопроса, касающихся метаклассов и множественного наследования. Первый: почему я получаю ошибку TypeError для класса Derived, но не для Derived2?

class Metaclass(type): pass

class Klass(object):
    __metaclass__  = Metaclass

#class Derived(object, Klass): pass # if I uncomment this, I get a TypeError

class OtherClass(object): pass

class Derived2(OtherClass, Klass): pass # I do not get a TypeError for this

Точное сообщение об ошибке:

TypeError: Error when calling the metaclass bases Cannot create a consistent method resolution order (MRO) for bases object, Klass

Второй вопрос: Почему в данном случае не работает super (если я использую __init__ вместо __new__, снова работает super):

class Metaclass(type):
    def __new__(self, name, bases, dict_):
        return super(Metaclass, self).__new__(name, bases, dict_)

class Klass(object):
    __metaclass__  = Metaclass

Там я получаю:

TypeError: Error when calling the metaclass bases type.__new__(X): X is not a type object (str)

Я использую Python 2.6.


person nils    schedule 04.02.2010    source источник


Ответы (4)


На второй вопрос уже дважды был дан хороший ответ, хотя __new__ на самом деле является статическим методом, а не методом класса, как ошибочно утверждается в комментарии...:

>>> class sic(object):
...   def __new__(cls, *x): return object.__new__(cls, *x)
... 
>>> type(sic.__dict__['__new__'])
<type 'staticmethod'>

Первый вопрос (как кто-то заметил) не имеет ничего общего с метаклассами: вы просто не можете умножать наследование от любых двух классов A и B в таком порядке, где B является подклассом A. Например:

>>> class cis(sic): pass
... 
>>> class oops(sic, cis): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
order (MRO) for bases sic, cis

MRO гарантирует, что самые левые базы посещаются раньше, чем самые правые, но также гарантирует, что среди предков, если x является подклассом y, то x посещается до y. Удовлетворить обе эти гарантии в данном случае невозможно. Конечно, для этих гарантий есть веская причина: без них (например, в классах старого стиля, которые гарантируют только порядок слева направо в разрешении методов, не ограничение подкласса) все переопределения в x были бы проигнорированы в пользу определений в y, и это не может иметь большого смысла. Подумайте об этом: что означает наследование сначала от object, а потом от какого-то другого класса? Что object (фактически несуществующее ;-) определение его нескольких специальных методов должно иметь приоритет над другим классом, что приводит к игнорированию переопределений другого класса?

person Alex Martelli    schedule 05.02.2010
comment
имеет смысл, поэтому, пока я не наследую напрямую от объекта (что на самом деле не имеет смысла, но я хотел быть уверенным, что ничего странного не происходит), все должно быть в порядке :) Интересно, можно ли использовать new без супер. - person nils; 05.02.2010
comment
@nils, да, __new__ можно использовать без super, за исключением сложных случаев множественного наследования. Если вы занимаетесь сложным множественным наследованием, __new__ более разумно. - person Alex Martelli; 05.02.2010

По первому вопросу взгляните на описание MRO в python. - в частности, раздел «Плохой порядок разрешения методов». По сути, это связано с тем, что python не знает, использовать ли объект или методы Klass. (Это не имеет ничего общего с использованием метаклассов.)

Что касается второго вопроса, похоже, вы неправильно понимаете, как работает функция __new__. Он не принимает ссылку на себя в качестве первого аргумента — он принимает ссылку на тип класса, экземпляр которого создается. Итак, ваш код должен выглядеть так:

class Metaclass(type):
    def __new__(cls, name, bases, dictn):
        return type.__new__(cls, name, bases, dictn)
person Smashery    schedule 04.02.2010

Для второго вопроса вам нужно передать себя __new__ следующим образом:

class Metaclass(type):
    def __new__(self, name, bases, dict_):
        return super(Metaclass, self).__new__(self, name, bases, dict_)

class Klass(object):
    __metaclass__  = Metaclass

Я не могу вспомнить, почему это так, но я думаю, что это потому, что type.__new__ не является связанным методом и, таким образом, волшебным образом не получает аргумент self.

person Benno    schedule 04.02.2010
comment
__new__ — это классовый метод, не используйте там self, это сбивает с толку - person John La Rooy; 05.02.2010
comment
Да, в коде, где я это сделал, я использовал cls или что-то подобное, но я следовал тому, что использовал оригинальный постер. - person Benno; 06.02.2010

Зачем тебе это?

class Derived(object, Klass):

Класс уже является производным от объекта.

class Derived(Klass):

Это разумная вещь здесь.

person Lennart Regebro    schedule 05.02.2010