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

Python type annotation for identical, generic function signatures

typing.Callable 接受两个 "arguments":参数类型和 return 类型。对于任意参数,参数类型应该是 ...,或者是显式类型列表(例如,[str, str, int])。

有没有一种方法可以表示 Callable 具有完全相同但任意的泛型签名?

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

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 中)不喜欢第一个位置的 ArgTCallable[..., RetT] 我能做到最好吗?

在 Python 3.10

之前

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

FuncT = TypeVar("FuncT", bound=Callable[..., object])

def fn_combinator(*fn: FuncT) -> FuncT:
    ...

Is there a way of representing Callables that have exactly the same, albeit arbitrary, signatures for generics?

与类型别名(例如:FuncT = Callable[..., RetT])不同,TypeVar 允许类型检查器推断参数与 return 值之间的依赖关系,确保函数签名将一模一样

然而,这种方法是完全有限的。使用 FuncT 很难正确键入 returned 函数(参见 this mypy issue)。

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)

由于 PEP 484.

中引入的 Callable 的限制,这是我们从 Python 3.7 开始所能做的最好的事情

... only a list of parameter arguments ([A, B, C]) or an ellipsis (signifying "undefined parameters") were acceptable as the first "argument" to typing.Callable. --- PEP 612


Python 3.10+

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

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

P = ParamSpec("P")
RetT = TypeVar("RetT")

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

它还允许我们在不使用 cast:

的情况下对 returned 调用进行完全类型检查
def fn_combinator(*fn: Callable[P, RetT]) -> Callable[P, RetT]:
    def combined_fn(*args: P.args, **kwargs: P.kwargs) -> RetT:
        ...

    return combined_fn

查看发行说明 here