如何在 Python 中注释相互依赖的函数参数和具有不同数量参数的返回类型(< 3.10)?

问题描述 投票:0回答:1

编辑注释1:到目前为止,我已经找到了PEP 612,它解决了这个问题 - 从Python 3.10开始 - 通过引入

typing.ParamSpec
。所以这个问题专门针对 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, Y], Tuple[Z, <some type>]]:
   ...

,但我的

transform
函数比这更通用,我在具有不同数量参数的函数上使用它,然后我不知道如何在
Callable
的第一个参数中指定它(其中有
[X, Y] 
上面)。

(如何)在引入

typing.ParamSpec
之前可以做到这一点吗?

python python-typing higher-order-functions
1个回答
3
投票

在 Python 3.10 之前,这似乎无法优雅且完全地完成,正是因为这个原因,Python 3.10 引入了

ParamSpec
(根据 MisterMiyagi 的评论)。

但是,可以通过对不同数量的参数使用重载(参见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
© www.soinside.com 2019 - 2024. All rights reserved.