Strange metaclass behaviour
Ziga Seilnacht
ziga.seilnacht at gmail.com
Thu Mar 23 09:30:55 EST 2006
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
More information about the Python-list
mailing list