Вот простой пример кода:
class meta(type): pass
class Test(str, metaclass = meta): pass
Когда я запускаю на нем mypy (только из cli, без каких-либо флагов или других дополнительных настроек), я вижу следующий результат:
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]
Я знаю, что аналогичная ошибка возникает во время выполнения, когда метакласс производного класса не является подтипом всех родительских метаклассов. Но type(str) is type и мой класс meta произошел от type, поэтому я не могу понять, что здесь не так.
Приведенный пример работает не только с родительским классом str. Mypy говорит, что при наследовании от int или float все правильно.
Итак, как избавиться от такого сообщения об ошибке? Или это просто ошибка mypy?
К сожалению, в документации по метаклассам mypy нет ответа: mypy.readthedocs.io/en/stable/metaclasses.html






Я думаю, это потому, что strнаследует от Sequence[str], который сам наследует от протокола времени проверки типа типа _SpecialForm:
# builtins.pyi
class str(Sequence[str]): ...
# typing.pyi
Protocol: _SpecialForm
class Collection(Iterable[_T_co], Container[_T_co], Protocol[_T_co]): ...
class Sequence(Collection[_T_co], Reversible[_T_co]): ...
Я не уверен, прав ли Mypy здесь или нет (если это зависит от реализации, то это также невозможно).
Следует отметить, что это также вызывает другие ошибки того же типа для bytes, Enum и ABC, но не для Fraction, который наследует от Rational, протокола:
# numbers.pyi
class _RealLike(_ComplexLike, Protocol): ...
class Real(Complex, _RealLike): ...
class Rational(Real): ...
# fractions.pyi
class Fraction(Rational): ...
Для сравнения, Пайрайт говорит, что str и bytes подходят, а Fraction нет; он согласен с Mypy по Enum и ABC.
str не «наследует» от Sequence[str] — он наследует от типа — он зарегистрирован как виртуальный подкласс Sequence[str]. Порог, позволяющий узнать, является ли это ошибкой Mypy или верен, прост: если это реальная ошибка времени выполнения, то она неверна. Если он работает так, как задумано, и mypy помечает это как ошибку (рассматриваемый случай), то это ошибка mypy.
@jsbueno, ты не можешь протестировать это таким образом. многие вещи могут быть неправильными с точки зрения набора текста, но при этом работать без ошибок
@jsbueno Во время выполнения str является вещью C и зарегистрирован как виртуальный подкласс Sequence[str], конечно, но во время проверки типа «виртуальный подкласс» (или даже «класс») не имеет значения; согласно определению, str — это символ в системе типов, тип, который реализует/наследует Sequence[str]. Кроме того, ваш порог довольно нереален. Система статического типа не обязательно должна согласовываться со своим аналогом во время выполнения.
«Система статического типа не обязательно должна согласовываться со своим аналогом во время выполнения», да. так что, когда возникает ошибка, а программа работает корректно, следует игнорировать это, а не тратить время на поиск обходных путей, чтобы система типизации понимала, что вы хотите сделать (вернее, что вы уже делаете).
str статически типизирован как подкласс typing.Protocol, метакласс которого mypy имеет специальный регистр как typing._ProtocolMeta (typing.Protocol на самом деле является экземпляром typing._ProtocolMeta во время выполнения, но это не отражается в заглушках ввода, поэтому обычно mypy не узнает об этом).
Поэтому, чтобы точно отразить в вашем примере как статическую типизацию, так и время выполнения, вы можете сделать это (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
Поскольку typing._ProtocolMeta имеет минимальный интерфейс типизации, это дает вам относительно свободу реализации meta без особого обслуживания, если средства проверки типов и/или среда выполнения Python решат изменить метакласс str в будущем.
Обратите внимание, что аргументы ключевых слов в объявлении класса в настоящее время не работают должным образом в mypy для пользовательских метаклассов, поэтому вы столкнетесь с некоторыми проблемами безопасности типов для них. Вместо использования метаклассов рассмотрите возможность использования базового класса, определяющего __init_subclass__.
Интересно, но _ProtocolMeta закрыто, поэтому это не идеальное решение, которое мы хотели бы видеть.
Действительно @NoamNol, но поскольку его использование ограничено TYPE_CHECKING, у вас никогда не возникнет проблем во время выполнения, даже если детали реализации изменятся или символ вообще перестанет существовать. Пока ваш процесс CI (со статической типизацией) фиксирует любые изменения, исправления должны быть однострочными.
Было бы здорово просто написать class meta(type(str)): pass, но mypy отклоняет это с помощью Unsupported dynamic base class "type"
Не существует способа, которым «статическая проверка типов» могла бы когда-либо использовать возможности языка времени выполнения. Это проблема mypy, которую они могут попытаться обойти, а могут и не попытаться обойти, чтобы соответствовать поведению во время выполнения в ближайшие годы. Я бы не стал этого ждать. Проблема в том, что MyPy увидит, что виртуальный подкласс, добавленный к str для завершения проверки типов, будет считаться «настоящим» подклассом.