solving the metaclass conflict

Michele Simionato mis6 at pitt.edu
Sun Jun 8 18:37:25 EDT 2003


pje at telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.0306081024.517d855e at posting.google.com>...> 
> Anyway...  the punchline to all of this is...  I quickly discovered
> that for normal usage (i.e. not via automated tools like my AOP code),
> you don't need even as sophisticated of a recipe as what you've
> created, let alone what I wrote.  It's just as easy to do:
> 
> class C1(B1,B2):
>     class __metaclass__(B1.__class__,B2.__class___): pass
> 
> While a bit verbose, I find that about 90% of the time when I need to
> combine metaclasses, there are only two involved, and then the
> inheritance hierarchy stabilizes again.  Perhaps you should mention
> this "manual recipe" along with your automatic one, since it's a lot
> less than 25 lines if you only need to do it once or twice.

Point taken. I wanted something automatic for an AOP-like tool, but it is
true that in most case you don't need it.

Here is my last version:

def memoize(f):
    """This closure stores all f invocations in a dictionary; notice that
    f cannot have keywords arguments or dictionaries as arguments."""
    dic={} # internal dictionary
    wrapped_f=lambda *args: dic.setdefault(args,f(*args))
    wrapped_f.dic=dic # make dic available outside
    return wrapped_f

def _generatemetaclass(bases,metas,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
    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,{})

_generatemetaclass=memoize(_generatemetaclass) 

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
    return lambda n,b,d: _generatemetaclass(b,metas,priority)(n,b,d)

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

from oopp import pretty
print pretty(_generatemetaclass.dic)

#print _generatemetaclass.result
#print _generatemetaclass.argskw

class D(A,B):
    __metaclass__=clsfactory()


print type(D) is type(C)

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

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

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




More information about the Python-list mailing list