4

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.

Community
  • 1
  • 1
Mark Booth
  • 7,605
  • 2
  • 68
  • 92
  • 2
    What's wrong with `class BD2( B, D ): pass`? Why isn't that a perfectly good "mixin factory"? What's wrong with the available mixin syntax? – S.Lott Jan 31 '12 at 21:26
  • Related: http://stackoverflow.com/questions/7057019/python-dynamic-inheritance-how-to-choose-base-class-upon-instance-creation/7058640#7058640 – Niklas R Jan 31 '12 at 22:02
  • Of course one __can__ return a class from a function in Python. You either define it inline in the function body, reference an exiting class in the global (or nonlocal) scope, or create one with an expression explicitly calling `type` or another metaclass. What is ilegal is trying to use the keyword `class` as a function call. – jsbueno Feb 01 '12 at 15:02
  • @MarkBooth: They're one-line class definitions. I can't see how anything could be cleaner. And they don't repeat the class name they way your example does, which strikes me as a valuable example of DRY. – S.Lott Feb 01 '12 at 18:59
  • @MarkBooth: I'm not sure that complex code with PEP 8 compliance is all that good an idea. I think that justifying more complex code because of slavish devotion to PEP 8 is a costly mistake. – S.Lott Feb 02 '12 at 14:40
  • @MarkBooth: Shorter yes. Cleaner no. I asked one question: what's wrong with a class definition and instantiation with **no** additional programming. You've given one reason: shorter code. And you've only justified that with a slavish devotion to PEP 8. I asked. I got my answer. I don't think it's a very good answer. But that's not the point. I wanted to know why. Eventually, you explained why this is so important. – S.Lott Feb 02 '12 at 15:10
  • @S.Lott - No problem, glad I could make my question clearer. – Mark Booth Feb 02 '12 at 17:07

2 Answers2

7

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
Community
  • 1
  • 1
Ben
  • 68,572
  • 20
  • 126
  • 174
  • 3
    Aren't you using `base` and `mixin` in the wrong order? Usually a mixin would go before a base class in the class definition. I'd say something along the lines of `type(name, (mixin, base), {})` would be better. – seddonym Jun 18 '15 at 10:43
5

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), {})
ejucovy
  • 907
  • 9
  • 10