对 ParamSpec 变量应用转换?

apply transformation on a ParamSpec variable?

我有什么方法可以对 ParamSpec 应用转换吗?我可以用一个例子来说明问题:

from typing import Callable

def as_upper(x: str):
    return x.upper()

def eventually(f: Callable[P, None], *args: P.args, **kwargs: P.kwargs):
    def inner():
        def transform(a):
            return a() if isinstance(a, Callable) else a
        targs = tuple(transform(a) for a in args)
        tkwargs = {k: transform(v) for k,v in kwargs.items()}
        return f(*targs, **tkwargs)
    return inner

eventually(as_upper, lambda: "hello")  # type checker complains here

这个类型检查器(在我的例子中是 pyright)会抱怨这个。函数 eventually 收到可调用的 () -> str 而不是预期的 str。我的问题是:有什么方法可以让我指定它应该期望 () -> str 而不是 str 本身吗?一般来说,如果一个函数需要一个类型 T 我可以将它转换为(比如)() -> T?

我基本上是在问是否有可能以某种方式转换 ParamSpec,以便相关函数不需要相同的参数,而是“几乎”相同的参数。

我真的不希望这成为可能,但也许有更多类型检查经验的人知道这个问题的潜在解决方案。 :)

当前可用的工具无法正确输入此装饰器 (Python 3.10)。

这里有两个主要问题:

  • ParamSpecConcatenate目前只允许我们修改固定数量的参数。
  • 我们不能连接 keyword-only 个参数(这使得转换 **kwargs: P.kwargs 不可能)

但是,在这些约束下,如果位置参数已知,我们可以利用柯里化实现一个不太优雅的解决方案:

from typing import Callable, Concatenate, ParamSpec, TypeVar

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

def something(a: str, b: int, c: bool):
    return f"{a} {b} {c}"

def eventually(
    f: Callable[Concatenate[T, P], RetT], a: Callable[[], T]
) -> Callable[P, RetT]:
    def inner(*args: P.args, **kwargs: P.kwargs) -> RetT:
        transformed = a() if callable(a) else a
        return f(transformed, *args, **kwargs)

    return inner


something_eventually = eventually(
    eventually(eventually(something, lambda: "hello"), lambda: 2), lambda: False
)
something_eventually()  # hello 2 False

请注意,目前还无法连接关键字参数 (See also)。

eventually也可以用更函数化的方式来应用(但不能正确打字):

from functools import reduce

# Though it unfortunately doesn't type check
something_eventually = reduce(
    eventually,
    [lambda: "hello", lambda: 2, lambda: False],
    something,
)
something_eventually()  # hello 2 False

reduce 期望值的类型具有相同的类型,而我们在每次迭代中更改函数的类型。这使得当我们以这种方式应用 eventually 任意次时无法键入它。

reduce 只是一个例子。从更一般的意义上说,我怀疑我们是否可以正确地输入一些涉及重复应用柯里化函数的东西,比如上面提出的 eventually,至少在当前的 Concatenate 支持下。