对 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)。
这里有两个主要问题:
ParamSpec
和Concatenate
目前只允许我们修改固定数量的参数。
- 我们不能连接 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
支持下。
我有什么方法可以对 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)。
这里有两个主要问题:
ParamSpec
和Concatenate
目前只允许我们修改固定数量的参数。- 我们不能连接 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
支持下。