MyPy отображает ошибку при наследовании от str и добавлении метакласса

Вот простой пример кода:

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 увидит, что виртуальный подкласс, добавленный к str для завершения проверки типов, будет считаться «настоящим» подклассом.

jsbueno 08.06.2024 21:20

К сожалению, в документации по метаклассам mypy нет ответа: mypy.readthedocs.io/en/stable/metaclasses.html

Noam-N 09.06.2024 13:36
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
2
139
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я думаю, это потому, что 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 08.06.2024 21:18

@jsbueno, ты не можешь протестировать это таким образом. многие вещи могут быть неправильными с точки зрения набора текста, но при этом работать без ошибок

Noam-N 08.06.2024 21:32

@jsbueno Во время выполнения str является вещью C и зарегистрирован как виртуальный подкласс Sequence[str], конечно, но во время проверки типа «виртуальный подкласс» (или даже «класс») не имеет значения; согласно определению, str — это символ в системе типов, тип, который реализует/наследует Sequence[str]. Кроме того, ваш порог довольно нереален. Система статического типа не обязательно должна согласовываться со своим аналогом во время выполнения.

InSync 08.06.2024 21:35

«Система статического типа не обязательно должна согласовываться со своим аналогом во время выполнения», да. так что, когда возникает ошибка, а программа работает корректно, следует игнорировать это, а не тратить время на поиск обходных путей, чтобы система типизации понимала, что вы хотите сделать (вернее, что вы уже делаете).

jsbueno 08.06.2024 22:13
Ответ принят как подходящий

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 закрыто, поэтому это не идеальное решение, которое мы хотели бы видеть.

Noam-N 09.06.2024 12:51

Действительно @NoamNol, но поскольку его использование ограничено TYPE_CHECKING, у вас никогда не возникнет проблем во время выполнения, даже если детали реализации изменятся или символ вообще перестанет существовать. Пока ваш процесс CI (со статической типизацией) фиксирует любые изменения, исправления должны быть однострочными.

dROOOze 09.06.2024 13:10

Было бы здорово просто написать class meta(type(str)): pass, но mypy отклоняет это с помощью Unsupported dynamic base class "type"

Noam-N 09.06.2024 13:15

Другие вопросы по теме