Skip to content

Latest commit

 

History

History
106 lines (70 loc) · 2.78 KB

File metadata and controls

106 lines (70 loc) · 2.78 KB

Metaclasses

A :ref:`metaclass <python:metaclasses>` 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 :py:class:`type`, but it's possible to use other metaclasses. Metaclasses allows one to create "a different kind of class", such as :py:class:`~enum.Enum`s, :py:class:`~typing.NamedTuple`s and singletons.

Mypy has some special understanding of :py:class:`~abc.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 :py:func:`six.with_metaclass` and :py:func:`@six.add_metaclass <six.add_metaclass>` 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 :py:class:`type` as potential metaclasses.