Skip to content Skip to sidebar Skip to footer

Is It Possible To Dynamically Create A Metaclass For A Class With Several Bases, In Python 3?

In Python 2, with a trick it is possible to create a class with several bases, although the bases have metaclasses that are not subclass of each other. The trick is that these meta

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?"