solving the metaclass conflict

Michele Simionato mis6 at
Sun Jun 8 18:24:44 CEST 2003

pje at (Phillip J. Eby) wrote in message news:<25b5433d.0306071443.63b27df6 at>...
> mis6 at (Michele Simionato) wrote in message news:<2259b0e2.0306070629.7f5fcad7 at>...
> > I have just posted a recipe on the online Cookbook that could be of interest
> > for metaclasses users.
> > 
> > ----------------------------------------------------------------------------
> > 
> > Description:
> > 
> > Any serious user of metaclasses has been bitten at least once by the 
> > infamous metaclass/metatype conflict. Here I give a general recipe 
> > to solve the problem, as well as some theory and some examples.
> > 
> >  -->
> > 

I think I have fixed the meta-metaclass conflict and the other issue with
unneeded metaclasses, without adding additional lines. Here is the last
version of my class factory:


def memoize(f):
    """This closure remembers all f invocations"""
    argskw,result = [],[]
    def _(*args,**kw): 
        try: # returns a previously stored result
            return result[argskw.index(akw)]
        except ValueError: # there is no previously stored result
            argskw.append(akw) # update argskw
            result.append(f(*args,**kw)) # update result
            return result[-1] # return the new result
    _.argskw=argskw #makes the argskw list accessible outside
    _.result=result #makes the result list accessible outside
    return _

def clsfactory(*metas,**options):
    """Class factory avoiding metatype conflicts. The invocation syntax is
    clsfactory(M1,M2,..,priority=1)(name,bases,dic). If the base classes have 
    metaclasses conflicting within themselves or with the given metaclasses, 
    it automatically generates a compatible metaclass and instantiate it. 
    If priority is True, the given metaclasses have priority over the 
    bases' metaclasses"""

    priority=options.get('priority',False) # default, no priority
    trivial=lambda m: sum([issubclass(M,m) for M in metas],m is type) 
    # hackish!! m is trivial if it is 'type' or, in the case explicit 
    # metaclasses are given, if it is a superclass of at least one of them
    def generatemetaclass(bases,metas,priority):
        metabases=tuple([mb for mb in map(type,bases) if not trivial(mb)])
        metabases=(metabases+metas, metas+metabases)[priority]
        metaname="_"+''.join([m.__name__ for m in metabases])
        if not metabases: # trivial metabase
            return type 
        elif len(metabases)==1: # single metabase
            return metabases[0]
        else: # multiple metabases
            return clsfactory()(metaname,metabases,{})

    return lambda n,b,d: generatemetaclass(b,metas,priority)(n,b,d)

Notice that I am not going to change the Cookbook recipe yet, since I haven't
tested very well this new version. However, it seems to be working better
than the previous one:

# solving the meta-metaclass conflict
class MM1(type): pass
class MM2(type): pass
class M1(type):
class M2(type): 
class A: __metaclass__=M1
class B: __metaclass__=M2
class C(A,B):
print C,type(C),type(type(C))
#=> <class '__main__.C'> <class '__main__._M1M2'> <class '__main__._MM1MM2'>

# skipping unneeded metaclasses
class M(type): pass
class M1(M): pass

class B: __metaclass__=M
class C(B): 

print C,type(C),type(type(C))
#=> <class '__main__.C'> <class '__main__.M1'> <type 'type'>

If somebody found a case where the new version fails due to some
bug (which is quite possible ;-), please let me know.

Notice that (on purpose) I am not checking against David's example

    class A(type): pass
    class B(A): pass
    class C(type): pass
    M = _generatemetaclass((),(B,A,C),0)
    print M     #-> <class 'noconflict._BAC'>

since in my view only bases classes should be checked. In other words,
I am assuming the user is not perverse and s/he is passing non mutually
redundant metaclasses (whereas the metaclasses corresponding to the base
classes can be redundant). I could improve this later.


More information about the Python-list mailing list