如何在 Python (< 3.10) 中用不同数量的参数注释相互依赖的功能参数和 return 类型?
How to annotate interdependent functional parameter and return types with a varying number of parameters in Python (< 3.10)?
编辑说明 1:到目前为止,我发现 PEP 612 解决了这个问题 - 从 Python 3.10 开始 - 通过引入 typing.ParamSpec
.所以这个问题专门针对 Python 3.9 或更早版本。
编辑说明 2:原始示例过于狭窄,因为它的 return 类型与参数类型完全匹配,但问题实际上是关于更通用的情况,其中函数的参数签名相同但 return 类型不同。 (也欢迎允许不同参数签名的更通用的解决方案。)
问题:我正在使用一个转换函数,它接收一个函数作为参数,return另一个函数作为它的结果。传递的函数可以有任意数量和类型的参数(为简单起见,我们可以坚持使用位置参数),并且 returned 函数的调用方式与原始函数相同(具有相同数量和类型的参数) ,并且有一个 return 类型,它取决于传递函数的 return 类型(但不一定等于它;例如,它可以是一个包含原始值的元组 return 类型和某个给定类型的另一个值)。
我如何才能以反映已传递和 returned 函数签名的依赖性的方式注释转换函数?
一个简单的例子:
from typing import Callable
def transform(original_function: Callable) -> Callable:
def new_function(*args):
extra_payload = <do some calculation>
return original_function(*args), extra_payload
return new_function
def f(x: int, s: str) -> bool:
...
f(3, 'abc') # this is a valid call
f('abc', 3) # PyCharm warns about wrong argument types
# The goal is to have a warning on the following line:
transform(f)('abc', 3) # no warning with the mere Callable annotations above
有没有办法让PyCharm知道transform(f)
和f
有相同的参数签名?
如果转换后的函数有固定数量的参数我可以做到,例如(假设有两个参数):
from typing import TypeVar, Callable, Tuple
X = TypeVar('X')
Y = TypeVar('Y')
Z = TypeVar('Z')
def transform(original_function: Callable[[X, Y], Z]) -> Callable[[X, Y], Tuple[Z, <some type>]]:
...
,但我的 transform
函数比那个更通用,我在具有不同数量参数的函数上使用它,然后我不知道如何在 [= 的第一个参数中指定它18=](上面有 [X, Y]
)。
(如何)在引入typing.ParamSpec
之前可以做到这一点?
这似乎无法在 Python 3.10 之前优雅而完整地完成,正是由于这个原因 Python 3.10 引入了 ParamSpec
(根据 ).
但是,可以通过对不同数量的参数使用重载(参见 relevant part of PEP 484)来接近:
from typing import TypeVar, overload, Callable, Tuple
T = TypeVar('T')
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
Payload = <some type>
@overload
def transform(function: Callable[[], T]) -> Callable[[], Tuple[T, Payload]]:
...
@overload
def transform(function: Callable[[T1], T]) -> Callable[[T1], Tuple[T, Payload]]:
...
@overload
def transform(function: Callable[[T1, T2], T]) -> Callable[[T1, T2], Tuple[T, Payload]]:
...
@overload
def transform(function: Callable[[T1, T2, T3], T]) -> Callable[[T1, T2, T3], Tuple[T, Payload]]:
...
def transform(original_function):
def new_function(*args):
extra_payload = <do some calculation>
return original_function(*args), extra_payload
return new_function
编辑说明 1:到目前为止,我发现 PEP 612 解决了这个问题 - 从 Python 3.10 开始 - 通过引入 typing.ParamSpec
.所以这个问题专门针对 Python 3.9 或更早版本。
编辑说明 2:原始示例过于狭窄,因为它的 return 类型与参数类型完全匹配,但问题实际上是关于更通用的情况,其中函数的参数签名相同但 return 类型不同。 (也欢迎允许不同参数签名的更通用的解决方案。)
问题:我正在使用一个转换函数,它接收一个函数作为参数,return另一个函数作为它的结果。传递的函数可以有任意数量和类型的参数(为简单起见,我们可以坚持使用位置参数),并且 returned 函数的调用方式与原始函数相同(具有相同数量和类型的参数) ,并且有一个 return 类型,它取决于传递函数的 return 类型(但不一定等于它;例如,它可以是一个包含原始值的元组 return 类型和某个给定类型的另一个值)。
我如何才能以反映已传递和 returned 函数签名的依赖性的方式注释转换函数?
一个简单的例子:
from typing import Callable
def transform(original_function: Callable) -> Callable:
def new_function(*args):
extra_payload = <do some calculation>
return original_function(*args), extra_payload
return new_function
def f(x: int, s: str) -> bool:
...
f(3, 'abc') # this is a valid call
f('abc', 3) # PyCharm warns about wrong argument types
# The goal is to have a warning on the following line:
transform(f)('abc', 3) # no warning with the mere Callable annotations above
有没有办法让PyCharm知道transform(f)
和f
有相同的参数签名?
如果转换后的函数有固定数量的参数我可以做到,例如(假设有两个参数):
from typing import TypeVar, Callable, Tuple
X = TypeVar('X')
Y = TypeVar('Y')
Z = TypeVar('Z')
def transform(original_function: Callable[[X, Y], Z]) -> Callable[[X, Y], Tuple[Z, <some type>]]:
...
,但我的 transform
函数比那个更通用,我在具有不同数量参数的函数上使用它,然后我不知道如何在 [= 的第一个参数中指定它18=](上面有 [X, Y]
)。
(如何)在引入typing.ParamSpec
之前可以做到这一点?
这似乎无法在 Python 3.10 之前优雅而完整地完成,正是由于这个原因 Python 3.10 引入了 ParamSpec
(根据
但是,可以通过对不同数量的参数使用重载(参见 relevant part of PEP 484)来接近:
from typing import TypeVar, overload, Callable, Tuple
T = TypeVar('T')
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
Payload = <some type>
@overload
def transform(function: Callable[[], T]) -> Callable[[], Tuple[T, Payload]]:
...
@overload
def transform(function: Callable[[T1], T]) -> Callable[[T1], Tuple[T, Payload]]:
...
@overload
def transform(function: Callable[[T1, T2], T]) -> Callable[[T1, T2], Tuple[T, Payload]]:
...
@overload
def transform(function: Callable[[T1, T2, T3], T]) -> Callable[[T1, T2, T3], Tuple[T, Payload]]:
...
def transform(original_function):
def new_function(*args):
extra_payload = <do some calculation>
return original_function(*args), extra_payload
return new_function