我想实现一个可以使用一个或多个可调用对象作为参数来调用的函数,并让返回类型与最后提供的可调用对象的返回类型相同。
所以(没有类型提示)我希望能够调用例如
foo()
以下方式:
foo(lambda: "hello") # -> str
foo(lambda: "hello", str.upper) # -> str
foo(lambda: "hello", len) # -> int
可以这样实现:
def foo(first_fn, *more_fns):
result = first_fn()
for fn in more_fns:
result = fn(result)
return result
...如果您对类型提示不感兴趣。
但是试图以基本方式使
foo()
能够识别类型,我想写一些类似的内容:
from collections.abc import Callable
from typing import TypeVar, Any
T = TypeVar("T")
def foo(*fns: Callable[..., Any], fnN: Callable[..., T]) -> T:
...
不幸的是,不能再以同样的方式调用,因为
*fns
使 fnN
(在 *
之后)成为仅关键字参数。
我可以采用this方法并提供一堆像这样的显式签名:
def foo(
fn1: Callable[..., Any],
fn2: Callable[..., Any],
fnN: Callable[..., T],
) -> T:
return fnN(fn2(fn1()))
print(f"{foo(lambda: 1, lambda x: 2*x, lambda y: str(y))!r}")
...但这感觉不太好(因为它会限制可能的参数数量)。
另一种方法是反转调用逻辑并具有
foo(fn_N, ..., fn_1)
语义,但这会牺牲类型提示的直观签名。
也许我只是缺少一种语法方法来编写带有可变位置参数的函数,最后一个参数显式命名(以提供显式类型提示),同时保持其位置。
有什么想法吗?
是的,这可以使用PEP 646实现。这是实施草案:
from __future__ import annotations
import typing_extensions as t
if t.TYPE_CHECKING:
import collections.abc as cx
R = t.TypeVar("R")
FnsT = t.TypeVarTuple("FnsT")
@t.overload
def foo(first_fn: cx.Callable[[], R], /) -> R:
"""Handles the case where no other callables are passed in"""
@t.overload
def foo(
first_fn: cx.Callable[[], t.Any],
/,
*more_fns: t.Unpack[tuple[t.Unpack[FnsT], cx.Callable[[t.Any], R]]],
) -> R:
"""Handles the case where at least one extra callable is passed in"""
def foo(first_fn: cx.Callable[[], t.Any], /, *more_fns: t.Any) -> t.Any:
result = first_fn()
for fn in more_fns:
result = fn(result)
return result
>>> reveal_type(foo(lambda: "hello")) # mypy: Revealed type is "builtins.str"
>>> reveal_type(foo(lambda: "hello", str.upper)) # mypy: Revealed type is "builtins.str"
>>> reveal_type(foo(lambda: "hello", len)) # mypy: Revealed type is "builtins.int"
它的类型并不像人们希望的那样严格;变量
*more_fns
无法识别前面的可调用对象的返回类型可能与后续可调用对象的参数类型不兼容,但对于您的目的来说,这可能就足够了。