I have a number of classes which are wrapped by other classes to add new functionality.
Unfortunately, the wrapper classes don't implement pass through functions for the classes they are wrapping, so the wrapper cannot be used interchangeably with the original class.
I would like to dynamically create classes that contain the functionality of both the wrapper and the original class.
The idea I had was to create a mix-in class and use a factory to apply it to the existing class to create a new dual use class dynamically. This should allow me to write the mix-in once, and for the mixed class to be used to provide either the original functionality or the enhanced functionality from from the mix-in via one object.
This is the sort of thing I'm after:
class A:
def __init__(self):
self.name = 'A'
def doA(self):
print "A:", self.name
class B(A):
def __init__(self):
self.name = 'B'
def doB(self):
print "B:", self.name
class C(A):
def __init__(self):
self.name = 'C'
def doC(self):
print "C:", self.name
class D:
def doD(self):
print "D:", self.name
class BD(B,D):
pass
def MixinFactory(name, base_class, mixin):
print "Creating %s" % name
return class(base_class, mixin) # SyntaxError: invalid syntax
a, b, c, d, bd = A(), B(), C(), D(), BD()
bd2 = MixinFactory('BD2', B, D)()
cd = MixinFactory('CD', C, D)()
a.doA() # A: A
b.doA() # A: B
b.doB() # B: B
c.doA() # A: C
c.doC() # C: C
bd.doA() # A: B
bd.doB() # B: B
bd.doD() # D: B
bd2.doA() # A: B
bd2.doB() # B: B
bd2.doD() # D: B
cd.doA() # A: C
cd.doC() # C: C
cd.doD() # D: C
The problem is that obviously that you can't just return a class from a function. Ignoring the syntax error though, the above code does show what I'm trying to achieve.
I had a play with the three argument variant of type()
but couldn't get that to work, so I'm not sure if that is the right approach.
I assume that creating a mix-in factory of this type is possible in Python, so what do I need to understand to implement it?
As Niklas R commented, this answer to the question Python dynamic inheritance: How to choose base class upon instance creation? provides the solution to my query, but Ben's answer here provides a better explanation of why.
Actually you can return a class from a function. Your syntax error is that you're using the class keyword as if it were a function you can invoke.
Remember that all a class block for is create a new class and then bind the name you chose to it (in the current scope). So just put a class block inside your function, then return the class!
def mixinFactory(name, base, mixin):
class _tmp(base, mixin):
pass
_tmp.__name__ = name
return _tmp
As noted by ejucovy, you can also call type
directly:
def mixinFactory(name, base, mixin):
return type(name, (base, mixin), {})
This works because it's (usually) what a class block actually does; it collects all the names you define in the class block into a dictionary, then passes the name of the class, a tuple of the base classes, and the dictionary on to type
to construct the new class.
However, type
is nothing more than the default metaclass. Classes are objects like everything else, and are instances of a class. Most classes are instances of type
, but if there's another metaclass involved you should call that instead of type
, just as you wouldn't call object
to create a new instance of your class.
Your mixin (presumably) doesn't define a metaclass, so it should be compatible with any metaclass that is a subclass of type
. So you can just use whatever the class of base
is:
def mixinFactory(name, base, mixin):
return base.__class__(name, (base, mixin), {})
However, S.Lott's comment really seems like the best "answer" to this question, unless your mixin factory is doing anything more complicated than just creating a new class. This is much clearer AND less typing than any of the dynamic class creation variants proposed:
class NewClass(base, mixin):
pass
You can have class declarations anywhere, and class declarations can refer to variables for subclassing. As for the class name, it's just the .__name__
attribute on the class object. So:
def MixinFactory(name, base_class, mixin):
print "Creating %s" % name
class kls(base_class, mixin):
pass
kls.__name__ = name
return kls
should do it.
For a one-liner, the three-argument type
function should work too:
def MixinFactory(name, base_class, mixin):
return type(name, (base_class, mixin), {})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With