How to determine the metaclass of a class?

Question:

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?)

Asked By: Pro Q

||

Answers:

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.

Answered By: jsbueno

Here’s a helper function that can be used in such scenario:

def validate_abstract_methods(obj):
    abstract_methods = []
    for name in dir(obj):
        value = getattr(obj, name, None)
        if value is not None and getattr(value, '__isabstractmethod__', False):
            abstract_methods.append(name)
    if abstract_methods:
        abstract_methods.sort()
        raise TypeError(f"Can't instantiate abstract class {obj.__class__.__name__} with abstract methods {', '.join(abstract_methods)}")

This function roughly does the same thing as abc.ABC class – you just need to call it from your class’ __init__ method.

Answered By: Maciej Wiatrzyk
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.