Is It Possible To Dynamically Create A Metaclass For A Class With Several Bases, In Python 3?
Solution 1:
In Python 3 at the time the metaclass is used it have to be ready, and it can't know about the bases of the final (non-meta) class in order to dynamically create a metaclass at that point.
But instead of complicating things (I confess I could not wrap my head around your need for a meta-meta-class) - you can simply use normal class hierarchy with collaborative use of super
for your metaclasses.
You can even build the final metaclass dynamically with a simple
call to type
:
classA(type):
def__new__(metacls, name, bases,attrs):
attrs['A'] = "Metaclass A processed"returnsuper().__new__(metacls, name, bases,attrs)
classB(type):
def__new__(metacls, name, bases,attrs):
attrs['B'] = "Metaclass A processed"returnsuper().__new__(metacls, name, bases,attrs)
C = type("C", (A, B), {})
classExample(metaclass=C): pass
And:
In[47] :Example.A
Out[47]: 'Metaclass A processed'In[48]: Example.B
Out[48]: 'Metaclass A processed'
If your metaclasses are not designed to be collaborative in the first place, it will be very tricky to create any automatic method to combine them - and it would possibly involve monkey-patching the call to type.__new__
in some of the metaclasses constructors.
As for not needing to explictly build C
, you can use a normal function as the metaclass parameter, that will inspect the bases and build a dynamic derived metaclass:
defAuto(name, bases, attrs):
basemetaclasses = []
for base in bases:
metacls = type(base)
ifisinstance(metacls, type) and metacls isnottypeandnot metacls in basemetaclasses:
basemetaclasses.append(metacls)
dynamic = type(''.join(b.__name__ for b in basemetaclasses), tuple(basemetaclasses), {})
return dynamic(name, bases, attrs)
(This code is very similar to yours - but I used a three-line explicit for
instead of a set
in order to preserve the metaclass order - which might matter)
You have them to pass Auto as a metaclass for derived classes, but otherwise it works as in your example:
In [61]: classAA(metaclass=A):pass
In [62]: classBB(metaclass=B):pass
In [63]: classCC(AA,BB): pass
---------------------------------------------------------------------------
...
TypeError: metaclassconflict
...
In [66]: classCC(AA,BB, metaclass=Auto): pass
In [67]: type(CC)
Out[67]: __main__.AB
In [68]: CC.A
Out[68]: 'Metaclass A processed'
Solution 2:
Here's an example that shows some options that you have in python3.x. Specifically, C3
has a metaclass that is created dynamically, but in a lot of ways, explicitly. C4
has a metaclass that is created dynamically within it's metaclass function. C5
is just to demonstrate that it too has the same metaclass properies that C4
has. (We didn't lose anything through inheritance which can happen if you use a function as a metaclass instead of a type
...)
classMeta1(type):
deffoo(cls):
print(cls)
classMeta2(type):
defbar(cls):
print(cls)
classC1(object, metaclass=Meta1):
"""C1"""classC2(object, metaclass=Meta2):
"""C2"""classC3(C1, C2, metaclass=type('Meta3', (Meta1, Meta2), {})):
"""C3"""defmeta_mixer(name, bases, dct):
cls = type('MixedMeta', tuple(type(b) for b in bases), dct)
return cls(name, bases, dct)
classC4(C1, C2, metaclass=meta_mixer):
"""C4"""
C1.foo()
C2.bar()
C3.foo()
C3.bar()
C4.foo()
C4.bar()
classC5(C4):
"""C5"""
C5.foo()
C5.bar()
It should be noted that we're playing with fire here (the same way that you're playing with fire in your original example). There is no guarantee that the metaclasses will play nicely in cooperative multiple inheritance. If they weren't designed for it, chances are that you'll run into bugs using this at some point. If they were designed for it, there'd be no reason to be doing this hacky runtime mixing :-).
Solution 3:
In 95% of cases, it should be possible to use the machinery introduced in Python 3.6 due to PEP 447 to do most of what metaclasses can do using special new hooks. In that case, you will not need to combine metaclasses since your hooks can call super and their behavior is combined due to inheritance.
As for your general case, I believe that mgilson is right and that you are probably making things too complicated. I have yet to see a case for combining metaclasses that is not covered by PEP 447.
Post a Comment for "Is It Possible To Dynamically Create A Metaclass For A Class With Several Bases, In Python 3?"