Xop*_*ter 5 python generics type-annotation
typing.Callable有两个“参数”:参数类型和返回类型。对于任意参数,参数类型应该是...,或者是显式类型列表(例如,[str, str, int])。
有没有一种方法可以表示Callable具有完全相同(尽管是任意的)泛型签名的 s?
例如,假设我想要一个接受函数并返回具有相同签名的函数的函数,如果我预先知道函数签名,我可以这样做:
def fn_combinator(*fn:Callable[[Some, Argument, Types], ReturnType]) -> Callable[[Some, Argument, Types], ReturnType]:
...
Run Code Online (Sandbox Code Playgroud)
但是,我不预先知道参数类型,并且我希望我的组合器具有适当的通用性。我曾希望这会起作用:
ArgT = TypeVar("ArgT")
RetT = TypeVar("RetT")
FunT = Callable[ArgT, RetT]
def fn_combinator(*fn:FunT) -> FunT:
...
Run Code Online (Sandbox Code Playgroud)
然而,解析器(至少在 Python 3.7 中)不喜欢ArgT放在第一个位置。Callable[..., RetT]我能做的就是最好的吗?
如果您根本不需要更改函数签名,则应该定义FuncT为TypeVar:
FuncT = TypeVar("FuncT", bound=Callable[..., object])
def fn_combinator(*fn: FuncT) -> FuncT:
...
Run Code Online (Sandbox Code Playgroud)
有没有一种方法可以表示具有完全相同(尽管是任意的)泛型签名的可调用对象?
与类型别名(例如FuncT = Callable[..., RetT]:)不同,TypeVar它允许类型检查器推断参数和返回值之间的依赖关系,确保函数签名完全相同。
然而,这种方法是完全有限的。使用FuncT使得很难正确键入返回的函数(请参阅此 mypy 问题)。
def fn_combinator(*fn: FuncT) -> FuncT:
def combined_fn(*args: Any, **kwargs: Any) -> Any:
...
# return combined_fn # Won't work. `combined_fn` is not guaranteed to be `FuncT`
return cast(FuncT, combined_fn)
Run Code Online (Sandbox Code Playgroud)
由于PEP 484Callable中引入的限制,这是我们从 Python 3.7 开始能做的最好的事情。
...只有参数参数列表([A,B,C])或省略号(表示“未定义的参数”)可以作为 Typing.Callable 的第一个“参数”。--- PEP 612
幸运的是,可调用对象的类型注释在 Python 3.10 中变得更加灵活typing.ParamSpec(所谓的“参数规范变量”),并在PEP 612typing.Concatenate中提出。这扩展到支持注释更复杂的可调用对象。Callable
这意味着您将能够执行以下操作:
P = ParamSpec("P")
RetT = TypeVar("RetT")
def fn_combinator(*fn: Callable[P, RetT]) -> Callable[P, RetT]:
...
Run Code Online (Sandbox Code Playgroud)
它还允许我们对返回的可调用对象进行完全类型检查,而无需使用cast:
def fn_combinator(*fn: Callable[P, RetT]) -> Callable[P, RetT]:
def combined_fn(*args: P.args, **kwargs: P.kwargs) -> RetT:
...
return combined_fn
Run Code Online (Sandbox Code Playgroud)
请参阅此处的发行说明。