File: metaclasses.rst

package info (click to toggle)
mypy 1.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 22,388 kB
  • sloc: python: 114,600; ansic: 13,343; cpp: 11,380; makefile: 247; sh: 27
file content (117 lines) | stat: -rw-r--r-- 3,481 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
.. _metaclasses:

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:

Defining a metaclass
********************

.. code-block:: python

    class M(type):
        pass

    class A(metaclass=M):
        pass

.. _examples:

Metaclass usage example
***********************

Mypy supports the lookup of attributes in the metaclass:

.. code-block:: python

    from typing import ClassVar, TypeVar

    S = TypeVar("S")

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

        def make(cls: type[S]) -> S:
            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")

.. _limitations:

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:

.. code-block:: python

    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: metaclass conflict
    # 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

    class B12(A1, A2): pass  # Mypy Error: metaclass conflict

    # This can be solved via a common metaclass subtype:
    class CorrectMeta(M1, M2): pass
    class B2(A1, A2, metaclass=CorrectMeta): pass  # OK, runtime is also OK

* 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.
* ``Self`` is not allowed as annotation in metaclasses as per `PEP 673`_.

.. _PEP 673: https://peps.python.org/pep-0673/#valid-locations-for-self

For some builtin types, mypy may think their metaclass is :py:class:`abc.ABCMeta`
even if it is :py:class:`type` at runtime. In those cases, you can either:

* use :py:class:`abc.ABCMeta` instead of :py:class:`type` as the
  superclass of your metaclass if that works in your use-case
* mute the error with ``# type: ignore[metaclass]``

.. code-block:: python

    import abc

    assert type(tuple) is type  # metaclass of tuple is type at runtime

    # The problem:
    class M0(type): pass
    class A0(tuple, metaclass=M0): pass  # Mypy Error: metaclass conflict

    # Option 1: use ABCMeta instead of type
    class M1(abc.ABCMeta): pass
    class A1(tuple, metaclass=M1): pass

    # Option 2: mute the error
    class M2(type): pass
    class A2(tuple, metaclass=M2): pass  # type: ignore[metaclass]