Вопрос или проблема
Запуск mypy на следующем коде не выявляет проблем.
from typing import TypeVar
S = TypeVar("S", int, float, complex)
def func(x: list[S], m: S) -> list[S]:
return [val * m for val in x]
out1: list[int] = func([1, 2, 3], 4)
out2: list[complex] = func([1., 2., 3.], 4.)
Но следующий код
from typing import TypeVar
Number = int | float | complex
S = TypeVar("S", bound=Number)
def func(x: list[S], m: S) -> list[S]:
return [val * m for val in x]
out1: list[int] = func([1, 2, 3], 4)
out2: list[complex] = func([1., 2., 3.], 4.)
выдает следующие ошибки
y.py:7: error: List comprehension has incompatible type List[Union[int, float, complex]]; expected List[S] [misc]
y.py:7: error: Unsupported operand types for * (likely involving Union) [operator]
Чтобы избежать проблем с mypy, я думаю, что мне нужно создать ограниченный TypeVar из Union (который я импортирую из другого пакета и, следовательно, который в принципе вне моего контроля). Есть ли способ это сделать? Есть ли другой способ (без #type: ignore) избежать получения ошибки mypy?
Ответ или решение
Вопрос: Как создать ограниченный TypeVar на основе Union в Python, чтобы избежать ошибок типизации в mypy?
Введение
При разработке программного обеспечения на Python и использовании типизации для обеспечения надежности кода, разработчики часто сталкиваются с различными проблемами, связанными с совместимостью типов. Один из распространенных вопросов — это создание ограниченного TypeVar
на основе Union
, когда типы в Union
приходят из сторонних пакетов. Это может вызывать ошибки при статическом анализе кода с помощью mypy. Мы рассмотрим ваш пример, объясним, почему возникает ошибка, и предложим решение.
Проблема с кодом
Вы представили два фрагмента кода с использованием TypeVar
. Первый код работает без ошибок, так как вы явно указали допустимые типы (int
, float
, complex
). Однако второй код генерирует ошибки при использовании bound
, основанного на Union
:
Number = int | float | complex
S = TypeVar("S", bound=Number)
Ошибка возникает из-за того, что Number
— это объединение типов, а не один конкретный тип. При создании TypeVar
с типом bound
, mypy ожидает, что тип будет неизменяемым.
Понимание ошибок
Когда вы используете S
с ограничением bound=Number
, mypy интерпретирует S
как определенный тип, который должен быть подтипом типа Number
. Однако в строках:
out1: list[int] = func([1, 2, 3], 4)
и
out2: list[complex] = func([1., 2., 3.], 4.)
вы передаете списки, которые содержат значения разных типов. Это приводит к несоответствиям между ожидаемым типом (который должен быть согласованным в рамках единого TypeVar
) и фактическими типами списков.
Решение проблемы
Чтобы обойти эту проблему, можно воспользоваться другим подходом, например, создать разные TypeVar
для каждого типа. Вот пример решения:
from typing import TypeVar, Union, List
# Определяем Union для поддержки всех чисел
Number = Union[int, float, complex]
# Создаём несколько TypeVar для каждого из типов
T_int = TypeVar('T_int', bound=int)
T_float = TypeVar('T_float', bound=float)
T_complex = TypeVar('T_complex', bound=complex)
def func(x: List[Number], m: Number) -> List[Number]:
return [val * m for val in x]
# Пример использования
out1: List[int] = func([1, 2, 3], 4) # Всё под контролем
out2: List[complex] = func([1., 2., 3.], 4.)
Заключение
При использовании TypeVar с Union в Python необходимо учитывать, что mypy требует жесткой типизации, и в случае использования bound
с Union
могут возникнуть ошибки. Предложенное решение подразумевает использование нескольких TypeVar
, что позволит избежать некоторых распространенных проблем. Это также позволит вам более гибко управлять типами в вашем коде, что в свою очередь улучшит его надежность и читаемость.
Если у вас возникнут дополнительные вопросы или нужна помощь с более сложными сценариями, не стесняйтесь обращаться за помощью.