如何解决如何在 Python (< 3.10) 中使用不同数量的参数注释相互依赖的功能参数和返回类型?
编辑说明 1:到目前为止,我发现 PEP 612 通过引入 typing.ParamSpec
解决了这个问题 - 从 Python 3.10 开始。所以这个问题专门针对 Python 3.9 或更早版本。
编辑注意 2:原始示例太窄,因为它的返回类型与参数类型完全匹配,但问题实际上是关于更通用的情况,其中参数签名函数相同,但返回类型不同。 (也欢迎允许不同参数签名的更通用的解决方案。)
问题: 我正在使用一个转换函数,它接收一个函数作为参数,并返回另一个函数作为其结果。传递的函数可以有任意数量和类型的参数(为简单起见,我们可以使用位置参数),返回的函数的调用方式与原始函数相同(具有相同数量和类型的参数),并具有返回值type 取决于传递函数的返回类型(但不一定等于它;例如,它可以是包含原始返回类型的值和某个给定类型的另一个值的元组)。
如何以反映传递和返回函数签名依赖性的方式对转换函数进行注释?
一个简单的例子:
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,Tuple[Z,<some type>]]:
...
,但我的 transform
函数比这更通用,我在具有不同数量参数的函数上使用它,然后我不知道如何在 {{1} 的第一个参数中指定它}(上面有Callable
)。
(如何)在引入 [X,Y]
之前可以做到这一点?
解决方法
因此,如果您希望签名更具表现力,您可以使用 "callback protocol",即使用具有 typing.Protocol
方法的 __call__
和您想要的签名:
import typing
X = typing.TypeVar('X',contravariant=True)
Y = typing.TypeVar('Y',covariant=True)
class MyCallable(typing.Protocol[X,Y]):
def __call__(self,*args: X) -> Y:
...
def transform(original_function: MyCallable[X,Y]) -> MyCallable[X,Y]:
def new_function(*args):
return original_function(*args)
return new_function
def foo(*args: int) -> str:
return str(sum(args))
def bar(a: int,b: int) -> int:
return a + b
transform(bar)
x: str = transform(foo)(1,2)
y: str = transform(foo)(1,'2')
在上面运行 mypy
:
test.py:25: error: Argument 1 to "transform" has incompatible type "Callable[[int,int],int]"; expected "MyCallable[<nothing>,<nothing>]"
test.py:28: error: Argument 2 to "__call__" of "MyCallable" has incompatible type "str"; expected "int"
所以,它抱怨第 25 行,
transform(bar)
因为 bar
没有可变参数。对于第 27 行:
y: str = transform(foo)(1,'2')
因为我们将错误类型的参数传递给返回的函数。
,这似乎无法在 Python 3.10 之前优雅而完整地完成,正是出于这个原因,Python 3.10 引入了 ParamSpec
(根据 MisterMiyagi's comment)。
但是,可以通过对不同数量的参数使用重载(参见 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],Payload]]:
...
@overload
def transform(function: Callable[[T1,T2],T]) -> Callable[[T1,T2,T3],Payload]]:
...
def transform(original_function):
def new_function(*args):
extra_payload = <do some calculation>
return original_function(*args),extra_payload
return new_function
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。