Упрощенный сценарий (детская площадка):
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]
в трех последних случаях?
Вот для чего нужен TypeVarTuple:
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
не может быть ограничено (пока), поэтому нам придется использовать некоторые хитрости:
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
?
@DanielWalker Итак, tuple[*Ts] | Never
равно tuple[*Ts]
.
хм... хотя решение кажется верным, я нахожу проблемы с реальным кодом; конкретно моя функция украшена @asynccontextmanager
, cfr mypy-play.net/…
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]
@VitoDeTullio Я думаю, что это ошибка Mypy. Pyright не выводит ошибок для одного и того же кода.
в качестве обходного пути я мог бы добавить кучу определений функций
@overload
, по одному для каждой комбинации типов кортежей, но это в лучшем случае громоздко.