6

I've read some tutorials on Python metaclasses. I've never used one before, but I need one for something relatively simple and all the tutorials seem geared towards much more complex use cases. I basically want to create a template class that has some pre-specified body, but takes its base class as a parameter. Since I got the idea from C++/D templates, here's an example of what the code I want to write would look like in C++:

template<class T>
    class Foo : T {
        void fun() {}
    }
martineau
  • 119,623
  • 25
  • 170
  • 301
dsimcha
  • 67,514
  • 53
  • 213
  • 334

2 Answers2

10

Although it certainly can be done with metaclasses, you can do what you want without them because in Python classes are themselves objects. The means that—surprisingly—essentially nothing more than an almost one-to-one translation of the C++ code is required. Besides being relatively uncomplicated because of this, it'll also work without modification in both Python 2 & 3.

def template(class_T):
    """Factory function to create subclasses of class_T."""

    class Foo(class_T):
        def fun(self):
            print('%s.fun()' % self.__class__.__name__)

    Foo.__name__ += '_' + class_T.__name__  # rename the subclass to reflect its heritage
    return Foo

class Base1:
    def bar(self):
        print('Base1.bar()')

class Base2:
    def bar(self):
        print('Base2.bar()')

Foo_Base1 = template(Base1)
print('Foo_Base1 base classes: {}'.format(Foo_Base1.__bases__))

Foo_Base2 = template(Base2)
print('Foo_Base2 base classes: {}'.format(Foo_Base2.__bases__))

subclass1 = Foo_Base1()
subclass1.fun()
subclass1.bar()
subclass2 = Foo_Base2()
subclass2.fun()
subclass2.bar()

Output:

Foo_Base1 base classes: (<class __main__.Base1 at 0x00A79C38>,)
Foo_Base2 base classes: (<class __main__.Base2 at 0x00A79DC0>,)
Foo_Base1.fun()
Base1.bar()
Foo_Base2.fun()
Base2.bar()

The code in the (unimaginatively-named) template() function is an example of what is commonly called a class factory or an implementation of the Factory pattern. So, incidentally, you might find my answer to the question What exactly is a Class Factory? informative.

Edit: Added code to create different class names for each subclass returned—which was inspired by @aaronasterling's insight (in a now deleted comment) about potential confusion when debugging if the class manufactured always has the same name.

Community
  • 1
  • 1
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Awesome, I guess metaclasses are overkill for something so simple. This solution makes tons of sense in hindsight, since types are first class objects in Python, and classes can be created at runtime. I guess I haven't gotten comfortable enough with the dynamic language way of thinking to come up w/ it on my own yet, though. – dsimcha Oct 06 '10 at 21:45
  • Templates *are* metaclasses in C++, a strongly-typed language. In a weakly-typed one like Python, where classes are also objects as you noted, it's often not necessary to go there -- but when you do, you're not limited to template arguments and can do some amazing things. – martineau Oct 06 '10 at 22:08
  • 6
    **Python is strongly-typed**, you can't add a string and an integer in Python like you'd do in a weakly-typed language like Javascript. Incidentally, **Python is also dynamically-typed**. – Lie Ryan Oct 06 '10 at 22:56
  • @Lie Ryan: You're quite right -- I used the wrong terms and got strong/weak confused with static/dynamic typing. Thanks for the correction. – martineau Oct 07 '10 at 04:34
0

This is meaningless in Python, since it does not have templates. My understanding of parameterized templates in C++ (which is rather vague, since it is many years since I have looked at them), is that it acts like a class factory, and can create a subclass of whatever class you give it that has additional methods or attributes added.

In Python you can do this with a factory function that takes a class and returns a new class at runtime:

In [1]: def subclassFactory(cls):
   ...:     class Foo(cls):
   ...:         def fun(self):
   ...:             return "this is fun"
   ...:     return Foo
   ...: 

In [2]: class A(object):
   ...:     pass
   ...: 

In [5]: C = subclassFactory(A)

In [6]: C
Out[6]: <class '__main__.Foo'>
In [7]: c = C()
In [9]: c.fun()
Out[9]: 'this is fun'
In [10]: isinstance(c, A)
Out[10]: True
Dave Kirby
  • 25,806
  • 5
  • 67
  • 84