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: > > Look at following code: > > class M_A (type) : > > def __new__ (meta, name, bases, dict) : > print "M.__new__", meta, name, bases > return super (M_A, meta).__new__ (meta, name, bases, dict) > > class M_B (M_A) : pass > > class A (object) : __metaclass__ = M_A > > class B (object) : __metaclass__ = M_B > > class D (A, B) : pass > > > One would expect that either > 1) D inherits the metaclass M_A from A > 2) or python raises the well known "Metatype conflict among bases" > error
No, there is no conflict in this case: since M_B is a subclass of M_A, the metaclass of D is M_B. I don't think this is bug either: the fact is that type.__new__ works differently from object.__new__, so that it is called twice in this case. Not sure if it could be avoided. Speaking of the metatype conflict, I realized a while ago that it is possible to avoid it automatically even at the pure Python level, without changing the C code for type.__new__. However, the solution is too complicate. According to the Zen of Python "If the implementation is hard to explain, it's a bad idea", thus I have never used it. Still, it is an interesting exercise if you are willing to risk the melting of your brain, so here is the code ;) # noconflict.py """Deep, **DEEP** magic to remove metaclass conflicts. ``noconflict`` provides the ``safetype`` metaclass, the mother of conflict-free metaclasses. I you do from noconflict import safetype as type on top of your module, all your metaclasses will be conflict safe. If you override ``__new__`` when you derive from ``safetype``, you should do it cooperatively.""" import inspect, types, __builtin__ try: set # python version >= 2.4 except NameError: # python version <= 2.3 from sets import Set as set def skip_redundant(iterable, skipset=None): "Redundant items are repeated items or items in the original skipset." if skipset is None: skipset = set() for item in iterable: if item not in skipset: skipset.add(item) yield item memoized_metaclasses_map = {} # utility function def remove_redundant(metaclasses): skipset = set([types.ClassType]) for meta in metaclasses: # determines the metaclasses to be skipped skipset.update(inspect.getmro(meta)[1:]) return tuple(skip_redundant(metaclasses, skipset)) ################################################################## ## now the core of the module: two mutually recursive functions ## ################################################################## def get_noconflict_metaclass(bases, left_metas, right_metas): """Not intended to be used outside of this module, unless you know what you are doing.""" # make tuple of needed metaclasses in specified priority order metas = left_metas + tuple(map(type, bases)) + right_metas needed_metas = remove_redundant(metas) # return existing confict-solving meta, if any if needed_metas in memoized_metaclasses_map: return memoized_metaclasses_map[needed_metas] # nope: compute, memoize and return needed conflict-solving meta elif not needed_metas: # wee, a trivial case, happy us meta = type elif len(needed_metas) == 1: # another trivial case meta = needed_metas[0] # check for recursion, can happen i.e. for Zope ExtensionClasses elif needed_metas == bases: raise TypeError("Incompatible root metatypes", needed_metas) else: # gotta work ... metaname = '_' + ''.join([m.__name__ for m in needed_metas]) meta = classmaker()(metaname, needed_metas, {}) memoized_metaclasses_map[needed_metas] = meta return meta def classmaker(left_metas=(), right_metas=()): def make_class(name, bases, adict): metaclass = get_noconflict_metaclass(bases, left_metas, right_metas) return metaclass(name, bases, adict) return make_class ################################################################# ## and now a conflict-safe replacement for 'type' ## ################################################################# __type__=__builtin__.type # the aboriginal 'type' # left available in case you decide to rebind __builtin__.type class safetype(__type__): """Overrides the ``__new__`` method of the ``type`` metaclass, making the generation of classes conflict-proof.""" def __new__(mcl, *args): nargs = len(args) if nargs == 1: # works as __builtin__.type return __type__(args[0]) elif nargs == 3: # creates the class using the appropriate metaclass n, b, d = args # name, bases and dictionary meta = get_noconflict_metaclass(b, (mcl,), right_metas=()) if meta is mcl: # meta is trivial, dispatch to the default __new__ return super(safetype, mcl).__new__(mcl, n, b, d) else: # non-trivial metaclass, dispatch to the right __new__ # (it will take a second round) return super(mcl, meta).__new__(meta, n, b, d) else: raise TypeError('%s() takes 1 or 3 arguments' % mcl.__name__) Michele Simionato -- http://mail.python.org/mailman/listinfo/python-list