After publishing my previous article, on Python metaclasses, I got a fair amount of feedback, and there was one point in particular I feel I should elaborate on. It may seem simple at first glance, but it is nonetheless worth discussing in a little more detail: “Why do I use the __init__ method in my metaclass? Would __new__ be more pythonic?” It’s a fair question, but as I show here, I think the __init__ method is better for most cases.
I must admit, all the articles I’ve read describing metaclasses use the __new__ method in their examples. Frankly, I used it too, in the previous version of GreenRocket library. Nevertheless, the main goal of the previous article was to show that we can use classes as regular objects, and it succeeded in this. But metaclasses are not limited to this use case. The Python documentation itself makes this point: “The potential uses for metaclasses are boundless. Some ideas that have been explored include logging, interface checking, automatic delegation, automatic property creation, proxies, frameworks, and automatic resource locking/synchronization.” So sometimes you really need the power of __new__ method:
>>> class Meta(type): ... def __new__(meta, name, bases, attrs): ... filtered_bases =  ... for base in bases: ... if isinstance(base, type): ... filtered_bases.append(base) ... else: ... print(base) ... return type.__new__(meta, name, tuple(filtered_bases), attrs) ... >>> class Test(object, 'WTF!?', 'There are strings in bases!'): ... __metaclass__ = Meta ... WTF!? There are strings in bases! >>> Test.__mro__ (<class '__main__.Test'>, <type 'object'>)
However, I still feel that you should avoid the __new__ constructor as much as you can, due to its major drawback: it significantly decreases flexibility. For example, what happens if you inherit a new class from another two with two different metaclasses?
>>> class AMeta(type): pass ... >>> class BMeta(type): pass ... >>> class A(object): __metaclass__ = AMeta ... >>> class B(object): __metaclass__ = BMeta ... >>> class C(A, B): pass ... Traceback (most recent call last): File "", line 1, in TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
As you can see, you get a conflict. You have to create a new metaclass based on both existing ones:
>>> class CMeta(AMeta, BMeta): pass ... >>> class C(A, B): __metaclass__ = CMeta ...
If these two metaclasses are defined with the __init__ method, it is simple:
>>> class CMeta(AMeta, Bmeta): ... def __init__(cls, name, bases, attrs): ... Ameta.__init__(cls, name, bases, attrs) ... Bmeta.__init__(cls, name, bases, attrs)
But if both of them define a __new__ method, a walk in the park suddenly becomes a run through hell. And this is not a hypothetical example. Try to mix in collections.Mapping to a model declaration class based on your favorite ORM. I was given just such a task on my previous project.
In conclusion: Use a __new__ method only if you are going to do something for which __init__ is unfeasible. And always, always think twice before copying code from examples. Even if the examples are from official documentation.
Originally posted on Dmitry Vakhrushev’s personal blog at www.kr41.net.