用于相同的通用函数签名的 Python 类型注释



typing.Callable接受两个"参数":参数类型和返回类型。对于任意参数,参数类型应为...或显式类型列表(例如,[str, str, int](。

有没有一种方法来表示具有完全相同(尽管是任意的(泛型签名的Callable

例如,假设我想要一个接受函数并返回具有相同签名的函数的函数,如果我预先知道函数签名,我可以这样做:

def fn_combinator(*fn:Callable[[Some, Argument, Types], ReturnType]) -> Callable[[Some, Argument, Types], ReturnType]:
...

但是,我不知道预先的参数类型,我希望我的组合器是适当的通用的。我曾希望这会起作用:

ArgT = TypeVar("ArgT")
RetT = TypeVar("RetT")
FunT = Callable[ArgT, RetT]
def fn_combinator(*fn:FunT) -> FunT:
...

但是,解析器(至少在Python 3.7中(不喜欢ArgT在第一个位置。Callable[..., RetT]是我能做的最好的事情吗?

Python 3.10 之前

如果根本不需要更改函数签名,则应将FuncT定义为TypeVar

FuncT = TypeVar("FuncT", bound=Callable[..., object])
def fn_combinator(*fn: FuncT) -> FuncT:
...

有没有一种方法来表示具有完全相同(尽管是任意的(泛型签名的可调用对象?

与类型别名(例如: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)

这是我们在 Python 3.7 中可以做的最好的事情,因为 PEP 484 中引入了Callable的限制。

。只有参数参数列表([A,B,C](或省略号(表示"未定义的参数"(可以作为键入的第一个"参数"。调用。--- PEP 612

<小时 />

Python 3.10+

幸运的是,在 Python 3.10 中使用typing.ParamSpec(所谓的"参数规范变量"(和 PEP 612 中提出的typing.Concatenate可调用对象的类型注释变得更加灵活。这扩展了Callable以支持注释更复杂的可调用对象。

这意味着您将能够执行以下操作:

P = ParamSpec("P")
RetT = TypeVar("RetT")
def fn_combinator(*fn: Callable[P, RetT]) -> Callable[P, RetT]:
...

它还允许我们在不使用cast的情况下完全类型检查返回的可调用对象:

def fn_combinator(*fn: Callable[P, RetT]) -> Callable[P, RetT]:
def combined_fn(*args: P.args, **kwargs: P.kwargs) -> RetT:
...
return combined_fn

请参阅此处的发行说明。

最新更新