Christian Eder wrote: > Hi, > > I think I have discovered a problem in context of > metaclasses and multiple inheritance in python 2.4, > which I could finally reduce to a simple example:
I don't know if this is a bug; but I will try to expain what is happening; here is an example similar to yours: >>> class M_A(type): ... def __new__(meta, name, bases, dict): ... print 'metaclass:', meta.__name__, 'class:', name ... return super(M_A, meta).__new__(meta, name, bases, dict) ... >>> class M_B(M_A): ... pass ... >>> class A(object): ... __metaclass__ = M_A ... metaclass: M_A class: A >>> class B(object): ... __metaclass__ = M_B ... metaclass: M_B class: B So far everything is as expected. >>> class C(A, B): ... __metaclass__ = M_B ... metaclass: M_B class: C If we explicitly declare that our derived class inherits from the second base, which has a more derived metaclass, everything is OK. >>> class D(A, B): ... pass ... metaclass: M_A class: D metaclass: M_B class: D Now this is where it gets interesting; what happens is the following: - Since D does not have a __metaclass__ attribute, its type is determined from its bases. - Since A is the first base, its type (M_A) is called; unfortunately this is not the way metaclasses are supposed to work; the most derived metaclass should be selected. - M_A's __new__ method calls the __new__ method of the next class in MRO; that is, super(M_1, meta).__new__ is equal to type.__new__. - In type.__new__, it is determined that M_A is not the best type for D class; it should be actually M_B. - Since type.__new__ was called with wrong metaclass as the first argument, call the correct metaclass. - This calls M_B.__new__, which again calls type.__new__, but this time with M_B as the first argument, which is correct. As I said, I don't know if this is a bug or not, but you can achieve what is expected if you do the following in your __new__ method (warning, untested code): >>> from types import ClassType >>> class AnyMeta(type): ... """ ... Metaclass that follows type's behaviour in "metaclass resolution". ... ... Code is taken from Objects/typeobject.c and translated to Python. ... """ ... def __new__(meta, name, bases, dict): ... winner = meta ... for cls in bases: ... candidate = type(cls) ... if candidate is ClassType: ... continue ... if issubclass(winner, candidate): ... continue ... if issubclass(candidate, winner): ... winner = candidate ... continue ... raise TypeError("metaclass conflict: ...") ... if winner is not meta and winner.__new__ != AnyMeta.__new__: ... return winner.__new__(winner, name, bases, dict) ... # Do what you actually meant from here on ... print 'metaclass:', winner.__name__, 'class:', name ... return super(AnyMeta, winner).__new__(winner, name, bases, dict) ... >>> class OtherMeta(AnyMeta): ... pass ... >>> class A(object): ... __metaclass__ = AnyMeta ... metaclass: AnyMeta class: A >>> class B(object): ... __metaclass__ = OtherMeta ... metaclass: OtherMeta class: B >>> class C(A, B): ... pass ... metaclass: OtherMeta class: C > Does anyone have a detailed explanation here ? > Is this problem already known ? > > regards > chris I hope that above explanation helps. Ziga -- http://mail.python.org/mailman/listinfo/python-list