Явное обобщенное расширение кортежа

Упрощенный сценарий (детская площадка):

from typing import TypeVar, reveal_type

class A: ...
class B(A): ...
class C(A): ...

T = TypeVar('T', bound=A)

def fun(*args: T) -> tuple[T, ...]: return args

# one parameter
reveal_type(fun(A()))  # Revealed type is "builtins.tuple[gargs.A, ...]"
reveal_type(fun(B()))  # Revealed type is "builtins.tuple[gargs.B, ...]"
reveal_type(fun(C()))  # Revealed type is "builtins.tuple[gargs.C, ...]"

# multiple parameters, same type
reveal_type(fun(A(), A()))  # Revealed type is "builtins.tuple[gargs.A, ...]"
reveal_type(fun(B(), B()))  # Revealed type is "builtins.tuple[gargs.B, ...]"
reveal_type(fun(C(), C()))  # Revealed type is "builtins.tuple[gargs.C, ...]"

# multiple parameters, different types
reveal_type(fun(A(), B()))  # Revealed type is "builtins.tuple[gargs.A, ...]"
reveal_type(fun(B(), C()))  # Revealed type is "builtins.tuple[gargs.A, ...]"
reveal_type(fun(C(), A()))  # Revealed type is "builtins.tuple[gargs.A, ...]"

есть ли способ аннотировать fun, чтобы научить mypy тому, что fun должен возвращать tuple[A, B], tuple[B, C] и tuple[C, A] в трех последних случаях?

в качестве обходного пути я мог бы добавить кучу определений функций @overload, по одному для каждой комбинации типов кортежей, но это в лучшем случае громоздко.

Vito De Tullio 22.05.2024 15:32
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
1
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот для чего нужен TypeVarTuple:

(playground)

from typing import TypeVarTuple

Ts = TypeVarTuple('Ts')

def fun(*args: *Ts) -> tuple[*Ts]: return args
reveal_type(fun(A()))       # tuple[A]
reveal_type(fun(B()))       # tuple[B]
reveal_type(fun(C()))       # tuple[C]

reveal_type(fun(A(), A()))  # tuple[A, A]
reveal_type(fun(B(), B()))  # tuple[B, B]
reveal_type(fun(C(), C()))  # tuple[C, C]

reveal_type(fun(A(), B()))  # tuple[A, B]
reveal_type(fun(B(), C()))  # tuple[B, C]
reveal_type(fun(C(), A()))  # tuple[C, A]

TypeVarTuple не может быть ограничено (пока), поэтому нам придется использовать некоторые хитрости:

(playground)

from typing import Any, Never, Protocol, TypeVar, TypeVarTuple

Fun = TypeVar('Fun')
Ts = TypeVarTuple('Ts')

class FunA(Protocol):
    def __call__(self, /, *args: A) -> Never: ...

def fun_decorator(fun: Fun) -> Fun | FunA:
    return fun

@fun_decorator
def fun(*args: *Ts) -> tuple[*Ts]: return args
reveal_type(fun(D()))            # error
reveal_type(fun(D(), A()))       # error
reveal_type(fun(C(), B(), D()))  # error

После украшения fun имеет тип ((*Ts) -> tuple[*Ts]) | ((A, ...) -> Never). Чтобы вызов был типобезопасным, аргументы должны удовлетворять обеим сигнатурам. Обе подписи принимают переменное количество аргументов, но последнее требует, чтобы все аргументы были типа A.

Возвращаемый тип определяется как tuple[*Ts] | Never, что соответствует tuple[*Ts], поскольку Never не имеет члена.

Какова цель Never?

Daniel Walker 22.05.2024 17:06

@DanielWalker Итак, tuple[*Ts] | Never равно tuple[*Ts].

InSync 22.05.2024 17:06

хм... хотя решение кажется верным, я нахожу проблемы с реальным кодом; конкретно моя функция украшена @asynccontextmanager, cfr mypy-play.net/…

Vito De Tullio 22.05.2024 17:39
main.py:11: error: Argument 1 to "asynccontextmanager" has incompatible type "Callable[[VarArg(*Ts)], AsyncIterator[tuple[*Ts]]]"; expected "Callable[[VarArg(Never), KwArg(Never)], AsyncIterator[Never]]" [arg-type]
Vito De Tullio 22.05.2024 17:39

@VitoDeTullio Я думаю, что это ошибка Mypy. Pyright не выводит ошибок для одного и того же кода.

InSync 22.05.2024 18:13

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