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 中)不喜欢第一个位置的 ArgT
。 Callable[..., 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。
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 中)不喜欢第一个位置的 ArgT
。 Callable[..., 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
:
def fn_combinator(*fn: Callable[P, RetT]) -> Callable[P, RetT]:
def combined_fn(*args: P.args, **kwargs: P.kwargs) -> RetT:
...
return combined_fn
查看发行说明 here。