Here's simple example of code:
class meta(type): pass
class Test(str, metaclass = meta): pass
When I run mypy on it (just from cli, without any flags or other additional setup) I'm seeing next output:
test.py:2: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [misc]
I know that similar error appears at runtime when metaclass of derived class is not a subtype of all parents metaclasses. But type(str) is type
, and my class meta
derived from type
, so I cannot understand what's incorrect here.
Provided example isn't working only with str
parent class. Mypy says everything is correct when inheriting from int
or float
.
So, how to get rid of such error message? Or it is just a mypy bug?
str
is statically-typed as a subclass of typing.Protocol
, whose metaclass is special-cased by mypy as typing._ProtocolMeta
(typing.Protocol
is actually an instance of typing._ProtocolMeta
at runtime, but this isn't reflected in the typing stubs, so normally mypy wouldn't know about this).
Therefore, to accurately reflect both static typing and runtime in your example, you can do this (mypy Playground, Pyright Playground):
import typing as t
if t.TYPE_CHECKING:
from typing import _ProtocolMeta
else:
_ProtocolMeta = type(str)
class meta(_ProtocolMeta): pass
class Test(str, metaclass=meta): pass
Since typing._ProtocolMeta
has a bare minimum typing interface, this leaves you relatively free to implement meta
without too much maintenance if type-checkers and/or the Python runtime decides to change the metaclass of str
in the future.
Note that keyword arguments in a class declaration do not currently work properly in mypy for user-defined metaclasses, so you'll run into some type-safety issues for those. Instead of using metaclasses, consider using a base class defining __init_subclass__
instead.