 |
Metaclasses are deeper magic than 99% of users should ever worry about. If you
wonder whether you need them, you don’t.”
—Tim Peters |
This quote illustrates the perceived complexity of metaclasses in python, they really aren’t tho, this post attempts to explain them in simple terms understandable to anyone familiar with OOP (familiarity with django won’t hurt).
Basically they are just classes whose instances are classes and their main purpose (same as every other classes) is to customize their instances.
class MyMeta(type):
def __new__(cls, name, bases, attrs):
print 'in MyMeta.__new__'
return type.__new__(cls, name, bases, attrs)
class Boo(object):
__metaclass__ = MyMeta
print 'end'
This is the most basic and completely useless example of metaclasses, the interesting things here:
- metaclasses are supposed to inherit from the default (for new style classes) metaclass, “type”
- class attribute __metaclass__ tells python what metaclass current class should use (doh)
- metaclass is executed as soon as the class is defined, that means that it can permanently change your class (thus any object using it uses the changed one)
But since all so far was just theory let’s have a look at the real world examples, i explored metaclasses through django, so the examples are from there
class ModelFormMetaclass(type):
def __new__(cls, name, bases, attrs):
...
class ModelForm(BaseModelForm):
__metaclass__ = ModelFormMetaclass
First a short explanation of ModelForm, basically you pass it a django Model (object abstracting SQL table) and it generates the html form for it. You use it by subclassing ModelForm passing it your Model class as one of the inputs. (yes this is inaccurate explanation, let’s keep it simple for the sake of explanation)
Let’s start with a reminder, since __metaclass__ is just a class attribute, so when you use ModelForm (subclassing it) your class inherits this metaclass, what they do there is parse the Model you provided and generate the form fields in your class, this way (as opposed to manually writing fields) you:
- have cleaner/less code
- are more future proof (when/if the Model changes you don’t have to change your form)
def modelform_factory(....):
....
return ModelFormMetaclass(class_name, (form,), form_class_attrs)
This is from the same file as above code snippet, and ilustrates another way to use the metaclasses to generate classes, the use case for this is that based on the model you can get arbitrary numbers of forms for it, to be able to do so you have to generate the ModelForm on the fly (since you don’t know what Model you are gonna get)
Another possible use case for the ability to generate the classes on the fly is to create a template tag to which you pass model you want and it spits out the form for it, as a side note, this requieres light python abuse (globals() and
inner classes).
Ok, now we know what metaclasses are, how to use them and are familiar with it’s real world examples, but what are the alternatives? Well, two actually __new__ and class decorators.
You can use __new__ to kinda customize the class before it’s created, the options are limited, but first of two real world examples is still possible, that probably says: i refuse to acknowledge metalcasses can do anything and i’d rather abuse something i’m familiar with. A real downside of this is that the code in __new__ is executed once per object creation, let’s say you create 10 objects (implementing some feature in metaclass as opposed to your classes __new__, skipping the use of metaclass), code for that feature is executed 10 times in __new__ as opposed to 1 time when done in metaclass. Here is the code on what am i talking about:
class M(type):
def __new__(*args, **kw):
print 'M.__new__'
return type.__new__(*args, **kw)
class A(object):
__metaclass__ = M
class B(object):
def __new__(*args, **kw):
print 'B.__new__'
return object.__new__(*args, **kw)
for i in xrange(10):
A()
B()
The other alternative are class decorators, the major problem with this is that they are only available from 2.6 up, the other problem with them (same as __new__), they don’t allow you to blatantly abuse python like metaclasses do (say, mess up MRO), on the up side i can’t really think of a real world use case where you would need more power than class decorators offer
So, to conclude use metaclasses when you want to do one time transformation of classes and listen to Tim, if you don’t know why use metaclasses, than don’t.