Создание ограниченного TypeVar из Union

Вопрос или проблема

Запуск 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, что позволит избежать некоторых распространенных проблем. Это также позволит вам более гибко управлять типами в вашем коде, что в свою очередь улучшит его надежность и читаемость.

Если у вас возникнут дополнительные вопросы или нужна помощь с более сложными сценариями, не стесняйтесь обращаться за помощью.

Оцените материал
Добавить комментарий

Капча загружается...