solving the metaclass conflict
Michele Simionato
mis6 at pitt.edu
Sun Jun 8 12:24:44 EDT 2003
pje at telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.0306071443.63b27df6 at posting.google.com>...
> mis6 at pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.0306070629.7f5fcad7 at posting.google.com>...
> > 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.
> >
> > --> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197
> >
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:
# noconflict.py
def memoize(f):
"""This closure remembers all f invocations"""
argskw,result = [],[]
def _(*args,**kw):
akw=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,{})
generatemetaclass=memoize(generatemetaclass)
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):
__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'>
# 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'>
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.
Michele
More information about the Python-list
mailing list