Search code examples
pythonpython-3.xclassmetaclass

How to determine the metaclass of a class?


I have a class object, cls. I want to know its metaclass. How do I do this?

(If I wanted to know its parent classes, I would do cls.__mro__. Is there something like this to get the metaclass?)


Solution

  • Ok - so, a class's metaclass is just its own "type", and can be given by type(cls) and other means such as cls.__class__.

    In Python 3.x there are no further ambiguities - as the syntax for creating a metaclass just passes it as a named parameter on the class declaration statement anyway.

    However, the syntax used for creating a metaclass in Python 2.x generates a side-effect that is worth noting.

    Upon doing

    class A(object):
        __metaclass__ = MyMeta
    

    The __metaclass__ attribute is set to that value in the actual class, even if the actual metaclass is another one.

    Consider:

    def class_pre_decorator(name, bases, namespace):
         # do something with namespace
         return type(name, bases, namespace)
    

    This is a callable that can be used in the metaclass declaration of both Python 2 and 3 - and it is valid. After resolving, the actual metaclass in both cases will simply be type. However, in Python 2.x, cls.__metaclass__ will point to the callable class_pre_decorator, even tough type(cls) returns type, which is the correct metaclass.(Note that using callables in this way, they will not be used agian when the class is further subclassed)

    There is no way in Python 3 to guess the callable actually used to instantiate a class if it gives no other hint (like setting an attribute on the class) that it was used:

    # python 2
    class A(object):
       __metaclass__ = class_pre_decorator
    

    On the console:

    In [8]: type(A)
    Out[8]: type
    
    In [9]: A.__metaclass__
    Out[9]: <unbound method A.class_pre_decorator>
    

    and

    # Python 3
    class A(metaclass=class_pre_decorator):
        pass
    

    And trying to read A.__metaclass__ will simply raise an AttributeError.