Skip to content

Commit 0756cbc

Browse files
committed
Docs: how to work around metaclass limitation
1 parent be3a646 commit 0756cbc

File tree

5 files changed

+44
-8
lines changed

5 files changed

+44
-8
lines changed

docs/source/error_code_list.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ must be a subclass of ``type``. Further, the class hierarchy must yield
231231
a consistent metaclass. For more details, see the
232232
`Python documentation <https://docs.python.org/3.13/reference/datamodel.html#determining-the-appropriate-metaclass>`_
233233

234+
Note that mypy's metaclass checking is limited and may produce false-positives.
235+
See also :ref:`limitations`.
236+
234237
Example with an error:
235238

236239
.. code-block:: python

docs/source/metaclasses.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,34 @@ so it's better not to combine metaclasses and class hierarchies:
8686
such as ``class A(metaclass=f()): ...``
8787
* Mypy does not and cannot understand arbitrary metaclass code.
8888
* Mypy only recognizes subclasses of :py:class:`type` as potential metaclasses.
89+
90+
For some builtin types, mypy assumes that their metaclass is :py:class:`abc.ABCMeta`
91+
even if it's :py:class:`type`. In those cases, you can either
92+
93+
* use :py:class:`abc.ABCMetaclass` instead of :py:class:`type` as the
94+
superclass of your metaclass if that works in your use case,
95+
* mute the error with ``# type: ignore[metaclass]``, or
96+
* compute the metaclass' superclass dynamically, which mypy doesn't understand
97+
so it will also need to be muted.
98+
99+
.. code-block:: python
100+
101+
import abc
102+
103+
assert type(tuple) is type # metaclass of tuple is type
104+
105+
# the problem:
106+
class M0(type): pass
107+
class A0(tuple, metaclass=M1): pass # Mypy Error: metaclass conflict
108+
109+
# option 1: use ABCMeta instead of type
110+
class M1(abc.ABCMeta): pass
111+
class A1(tuple, metaclass=M1): pass
112+
113+
# option 2: mute the error
114+
class M2(type): pass
115+
class A2(tuple, metaclass=M2): pass # type: ignore[metaclass]
116+
117+
# option 3: compute the metaclass dynamically
118+
class M3(type(tuple)): pass # type: ignore[metaclass]
119+
class A3(tuple, metaclass=M3): pass

mypy/checker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2809,7 +2809,7 @@ class C(B, A[int]): ... # this is unsafe because...
28092809
self.msg.base_class_definitions_incompatible(name, base1, base2, ctx)
28102810

28112811
def check_metaclass_compatibility(self, typ: TypeInfo) -> None:
2812-
"""Ensure that metaclasses of all parent types are compatible."""
2812+
"""Ensures that metaclasses of all parent types are compatible."""
28132813
if (
28142814
typ.is_metaclass()
28152815
or typ.is_protocol

mypy/errorcodes.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,7 @@ def __hash__(self) -> int:
261261
"General",
262262
default_enabled=False,
263263
)
264-
METACLASS: Final[ErrorCode] = ErrorCode(
265-
"metaclass",
266-
"Ensure that metaclass is valid",
267-
"General",
268-
)
264+
METACLASS: Final[ErrorCode] = ErrorCode("metaclass", "Ensure that metaclass is valid", "General")
269265

270266
# Syntax errors are often blocking.
271267
SYNTAX: Final[ErrorCode] = ErrorCode("syntax", "Report syntax errors", "General")

mypy/semanal.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2648,7 +2648,11 @@ def get_declared_metaclass(
26482648
elif isinstance(metaclass_expr, MemberExpr):
26492649
metaclass_name = get_member_expr_fullname(metaclass_expr)
26502650
if metaclass_name is None:
2651-
self.fail(f'Dynamic metaclass not supported for "{name}"', metaclass_expr, code=codes.METACLASS)
2651+
self.fail(
2652+
f'Dynamic metaclass not supported for "{name}"',
2653+
metaclass_expr,
2654+
code=codes.METACLASS,
2655+
)
26522656
return None, False, True
26532657
sym = self.lookup_qualified(metaclass_name, metaclass_expr)
26542658
if sym is None:
@@ -2677,7 +2681,9 @@ def get_declared_metaclass(
26772681
metaclass_info = sym.node
26782682

26792683
if not isinstance(metaclass_info, TypeInfo) or metaclass_info.tuple_type is not None:
2680-
self.fail(f'Invalid metaclass "{metaclass_name}"', metaclass_expr, code=codes.METACLASS)
2684+
self.fail(
2685+
f'Invalid metaclass "{metaclass_name}"', metaclass_expr, code=codes.METACLASS
2686+
)
26812687
return None, False, False
26822688
if not metaclass_info.is_metaclass():
26832689
self.fail(

0 commit comments

Comments
 (0)