Пытаясь создать рекурсивные типы для аннотирования вложенной структуры данных, я наткнулся на следующее.
Этот код верен согласно mypy:
IntType = int | list["IntType"] | tuple["IntType", ...]
StrType = str | list["StrType"] | tuple["StrType", ...]
def int2str(x: IntType) -> StrType:
if isinstance(x, list):
return list(int2str(v) for v in x)
if isinstance(x, tuple):
return tuple(int2str(v) for v in x)
return str(x)
Но не этот, который должен быть эквивалентен:
IntType = int | list["IntType"] | tuple["IntType", ...]
StrType = str | list["StrType"] | tuple["StrType", ...]
def bad_int2str(x: IntType) -> StrType:
if isinstance(x, (list, tuple)):
return type(x)(bad_int2str(v) for v in x) # error here
return str(x)
Сообщение об ошибке
line 6: error: Incompatible return value type (
got "list[int | list[IntType] | tuple[IntType, ...]] | tuple[int | list[IntType] | tuple[IntType, ...], ...]",
expected "str | list[StrType] | tuple[StrType, ...]"
) [return-value]
line 6: error: Generator has incompatible item type
"str | list[StrType] | tuple[StrType, ...]";
expected "int | list[IntType] | tuple[IntType, ...]" [misc]
Я предполагаю, что mypy может сделать вывод, что type(x)
— это либо list
, либо tuple
.
Это ограничение mypy или в этом коде есть что-то подозрительное?
Если да, то откуда ограничение?
Mypy — это средство проверки статического типа, что означает, что он не оценивает type(x)(bad_int2str(v) for v in x)
при запуске. Таким образом, Mypy не может знать, что тип выражения — это StrType
, при текущих ограничениях реализации. (Mypy считает, что это выражение list[IntType] | tuple[IntType, ...]
. См. Ограничения.)
Вы можете дать подсказку на type(x)
, например, в следующих примерах.
def good_int2str(x: IntType) -> StrType:
if isinstance(x, (list, tuple)):
ty: type[list | tuple] = type(x)
return ty(good_int2str(v) for v in x)
return str(x)
def another_good_int2str(x: IntType) -> StrType:
if isinstance(x, (list, tuple)):
return cast(type[list | tuple], type(x))(
another_good_int2str(v) for v in x)
return str(x)
Нет, мое описание не неверно. Mypy определяет правильный тип аргумента генератора, но не для всего выражения вызова конструктора.
Он тоже знает, что (list[<IntType>] | tuple[<IntType>, ...]
, хотя и не так, как ожидал ОП, С.Терляков объяснил почему), отсюда и ошибка «несовместимый тип возвращаемого значения».
Нет, это не так. Проверьте, что написано в ссылке: «Анализ Mypy ограничивается отдельными символами и не отслеживает взаимосвязи между символами».
Я не думаю, что это имеет какое-либо отношение к рассматриваемому вопросу. Вам нужно будет уточнить: какие «отдельные символы» и какие «отношения» не отслеживает Mypy?
Я имею в виду, что Mypy не может вывести конечный результат c(a)
равно StrType
, когда тип a
— Generator[StrType, None, None], а тип c
— type[list[<IntType>]] | type[tuple[<IntType>, ...]]
. В этом примере используются два символа: a
и c
.
... что возвращает нас к ответу С.Т. Терлякова, и нет, раздел «Ограничения» не посвящен отношениям между вызывающими и вызываемыми объектами, такими как ваши a
и c
. То, что Mypy не может вывести результат с помощью OP, не означает, что он «не может» узнать. Если бы этого не было, тип был бы Any
.
Для type(x)
не существует стирания типа.
Что должен сказать mypy о следующем?
x: list[int] = [1]
reveal_type(type(x))
Если мы спросим, там будет сказано:
Выявленный тип: «type[builtins.list[builtins.int]]».
Итак, когда вы запрашиваете type(x)(some_strtype_iterator)
, он справедливо жалуется, что вы пытаетесь создать list[StrType] | tuple[StrType, ...]
из итерации IntType
.
Обе ошибки намекают на этот факт: mypy
думает, что вы возвращаете список или кортеж IntType
, поскольку ошибка внутри выражения не приводит к потере уже известного типа. И это также указывает на то, что вы не можете построить список/кортеж IntType
из генератора, дающего StrType
- вы не собирались строить последовательность IntType
, но type(x)
требует этого.
Я только что начал обсуждение на типографском форуме, чтобы выяснить причины, по которым не удаляются дженерики для type(x)
.
Интересный. Оглядываясь назад, это имеет смысл. Есть ли еще где-нибудь задокументированные подобные «конфликты» между проверкой статического типа и поведением во время выполнения?
@Winks В этом случае поведение зависит от реализации. Pytype, например, передает ваш bad_int2str()
. Подобные случаи можно найти в различных обсуждениях, например, в вопросах GitHub, но полной документации пока нет (даже спецификаций).
«[...] Mypy не знает, что тип выражения — это
StrType
»: Неверно; он знает (reveal_type(bad_int2str(v) for v in x)
показываетGenerator[<StrType>, None, None]
), поэтому в первую очередь выдает ошибку «генератор имеет несовместимый тип элемента».