Permalink
Fetching contributors…
Cannot retrieve contributors at this time
107 lines (70 sloc) 2.74 KB

Metaclasses

A metaclass is a class that describes the construction and behavior of other classes, similarly to how classes describe the construction and behavior of objects. The default metaclass is type, but it's possible to use other metaclasses. Metaclasses allows one to create "a different kind of class", such as Enums, NamedTuples and singletons.

Mypy has some special understanding of ABCMeta and EnumMeta.

Defining a metaclass

class M(type):
    pass

class A(metaclass=M):
    pass

In Python 2, the syntax for defining a metaclass is different:

class A(object):
    __metaclass__ = M

Mypy also supports using the six library to define metaclass in a portable way:

import six

class A(six.with_metaclass(M)):
    pass

@six.add_metaclass(M)
class C(object):
    pass

Metaclass usage example

Mypy supports the lookup of attributes in the metaclass:

from typing import Type, TypeVar, ClassVar
T = TypeVar('T')

class M(type):
    count: ClassVar[int] = 0

    def make(cls: Type[T]) -> T:
        M.count += 1
        return cls()

class A(metaclass=M):
    pass

a: A = A.make()  # make() is looked up at M; the result is an object of type A
print(A.count)

class B(A):
    pass

b: B = B.make()  # metaclasses are inherited
print(B.count + " objects were created")  # Error: Unsupported operand types for + ("int" and "str")

Gotchas and limitations of metaclass support

Note that metaclasses pose some requirements on the inheritance structure, so it's better not to combine metaclasses and class hierarchies:

class M1(type): pass
class M2(type): pass

class A1(metaclass=M1): pass
class A2(metaclass=M2): pass

class B1(A1, metaclass=M2): pass  # Mypy Error: Inconsistent metaclass structure for 'B1'
# At runtime the above definition raises an exception
# TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

# Same runtime error as in B1, but mypy does not catch it yet
class B12(A1, A2): pass
  • Mypy does not understand dynamically-computed metaclasses, such as class A(metaclass=f()): ...
  • Mypy does not and cannot understand arbitrary metaclass code.
  • Mypy only recognizes subclasses of type as potential metaclasses.