Tag “Metaclasses”

After I had published my previous article, I got some feedback from my colleagues. And there was a simple (at first glance) but interesting question, that I am going to discuss. Why do I use __init__ method in my metaclass? Will __new__ one be more pythonic?

Indeed, all articles I have ever read describe metaclasses using __new__ method in their examples. Frankly, I used it too in the previous version of GreenRocket library. It was cargo cult. And I postponed publishing, before I had fixed that.

Nevertheless, the main goal of the previous article was to show, that we can use classes as regular objects. And it seems to be achieved. But metaclasses mechanism is not limited by this use case only. Python documentation says about it: “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 you really need the power of __new__ method sometimes:

>>> 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 am pretty sure, that you have to avoid __new__ as much as you can. Because 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 "<stdin>", line 1, in <module>
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 define just __init__ method, it will be 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 __new__ one, a walk in the park will turn to run through the 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 got such task on my previous project.

In conclusion. Use __new__ method only if you are going to do something, which is unfeasible in __init__ one. And think twice, before copying code from examples. Even if the examples are from official documentation.

Every article about Python metaclasses contains a quotation (yep, this one is not exception) by Tim Peters: “Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).” I completely disagree with this saying. Why? Because I hate magic. Moreover, I hate when something is explained using magic. Metaclasses are regular tools, and they are very useful in some cases. What cases? Let’s see.

As you know, classes in Python are full-featured objects. As any object, they are constructed using classes. Thus, the class which is used for constructing another class is called metaclass. By default, type is used in this role.

>>> class SomeClass(object):
...     pass
...
>>> SomeClass.__class__
<type 'type'>

When you need to get a custom metaclass, you should inherit it from type. Just like a regular class inherits object:

>>> class SomeMetaClass(type):
...     pass
...
>>> class AnotherClass(object):                            # Python 2.x syntax
...     __metaclass__ = SomeMetaClass
...
>>> class AnotherClass(object, metaclass=SomeMetaClass):   # Python 3.x syntax
...     pass
...
>>> AnotherClass.__class__
<class '__main__.SomeMetaClass'>

The syntax shown above usually confuses newbies. Because the magic is still there. Okay, forget about metaclasses. Let’s think about objects:

>>> obj = SomeClass()

What happens in this single line of code? We just create a new object of class SomeClass and assign the reference of this object to a variable obj. Clear. Let’s go on.

>>> AnotherClass = SomeMetaClass('AnotherClass', (object,), {})

And what is there? Exactly the same thing, but we create a class instead of a regular object. This is what happens in the magic syntax. The interpreter parses syntactic sugar of class declaration and executes it as shown above. The first parameter passed into metaclass call is a class name (it will be available under AnotherClass.__name__ attribute). The second one is a tuple of parent (or base) classes. And the third one is a body of class—its attributes and methods (it will accessible via AnotherClass.__dict__).

If you work with JavaScript, it should be familiar for you. There are no classes in JavaScript. Therefore, when you emulate them, you will have to call a factory function. The function returns an object, which will be used later as a class. Python metaclass works in the same but more convenient way.

The last question is why do we need this feature? Is simple inheritance not enough? Well, an example is the best explanation. Let’s take a look on GreenRocket library (hmm... implicit advertisement). Don’t worry, it is not about rocket science. It is a simple implementation of Observer design pattern. There are about 150 lines of code 70 of which are doc-strings.

You create a class of signals:

>>> from greenrocket import Signal
>>> class MySignal(Signal):
...     pass
...

Subscribe a handler on it:

>>> @MySignal.subscribe
... def handler(signal):
...     print('handler: ' + repr(signal))
...

Then create and fire a signal:

>>> MySignal().fire()
handler: MySignal()

...and the handler is called. Here is the body of subscribe method:

@classmethod
def subscribe(cls, handler):
    """ Subscribe handler to signal.  May be used as decorator """
    cls.logger.debug('Subscribe %r on %r', handler, cls)
    cls.__handlers__.add(handler)
    return handler

Look at cls.__handlers__ attribute. The library logic is based on the fact, that each signal class must have this attribute. If there had been no metaclasses in Python, the library would require explicit declaration of one in the following way:

>>> class MySignal(Signal):
...     __handlers__ = WeakSet()
...

But it is stupid copy-paste work. In addition, this is a bug prone solution:

>>> class MySecondSignal(MySignal):
...     pass
...

If user misses __handler__ attribute, MySecondSignal will actually use handlers of MySignal. Good luck in debug! That is why we need a metaclass there, it just does this work for us:

class SignalMeta(type):
    """ Signal Meta Class """

    def __init__(cls, class_name, bases, attrs):
        cls.__handlers__ = WeakSet()

As you can see, there is no magic. Of course, there are still some corner cases, which are not explained in the article. But I hope, it will be useful as a quick start for understanding of Python metaclasses.