在Python中键入curried函数
咖喱有这样一个功能。问题是我不知道如何让这个函数返回一个具有正确类型的修饰函数。救命啊,我还没有找到解决办法在Python中键入curried函数,python,functional-programming,Python,Functional Programming,咖喱有这样一个功能。问题是我不知道如何让这个函数返回一个具有正确类型的修饰函数。救命啊,我还没有找到解决办法 import functools import typing as ty from typing import TypeVar, Callable, Any, Optional F = TypeVar("F", bound=Callable[..., Any]) def curry(func: F, max_argc: Optional[int] = None): if ma
import functools
import typing as ty
from typing import TypeVar, Callable, Any, Optional
F = TypeVar("F", bound=Callable[..., Any])
def curry(func: F, max_argc: Optional[int] = None):
if max_argc is None:
max_argc = func.__code__.co_argcount
@functools.wraps(func)
def wrapped(*args):
argc = len(args)
if argc < max_argc:
return curry(functools.partial(func, *args), max_argc - argc)
else:
return func(*args)
return ty.cast(F, wrapped)
@curry
def foo(x: int, y: int) -> int:
return x + y
foo("df")(5) # mypy error: Too few arguments for "foo"
# mypy error: "int" not callable
# mypy error: Argument 1 to "foo" has incompatible type "str"; expected "int" # True
导入工具
将键入导入为ty
输入import TypeVar、Callable、Any和Optional
F=TypeVar(“F”,bound=Callable[…,Any])
def curry(函数:F,最大值:可选[int]=None):
如果max_argc为无:
max_argc=func.\u代码\u.co\u argcount
@functools.wrapps(func)
def包装(*参数):
argc=len(args)
如果argcint:
返回x+y
foo(“df”)(5)#mypy错误:“foo”的参数太少
#mypy错误:“int”不可调用
#mypy错误:“foo”的参数1具有不兼容的类型“str”;应为“int”#真
如何修复1,2个mypy错误?首先,我不会让它成为装饰程序,我会将它包装为
curry(foo)
。我发现在API中修饰的函数签名与其初始定义不同,这让人感到困惑
关于类型的主题,如果Python类型提示可以实现一般情况,我会印象深刻。我甚至不知道在Scala我会怎么做。对于以下两个参数的函数,可以使用重载
,执行有限的情况
T1 = TypeVar("T1")
T2 = TypeVar("T2")
U = TypeVar("U")
@overload
def curry(
func: Callable[[T1, T2], U],
max_argc: Optional[int]
) -> Callable[[T1], Callable[[T2], U]]:
...
为一个、三个、四个参数等添加版本。不管怎么说,有很多参数的函数都是代码味道,除了varargs,我不确定它对curry是否有意义。有趣的是,我尝试了完全相同的方法,编写了一个decorator,它将返回任何函数的curry版本,包括一般的高阶。 我试图构建一个
curry
,允许对输入参数进行任何分区
拒绝
然而,由于python类型系统的一些限制,这是不可能的。
我现在正在努力寻找一种通用的类型安全方法。我的意思是Haskell的生命,C++采用了它,Typescript也设法做到了,那么为什么Python不能?
我接触了mypy
的备选方案,比如pyright
,它有很棒的维护人员,但受PEP的约束,PEP声明有些事情是不可能的
愤怒
当我提交一个关于我的咖喱链中最后一块缺失的pyright
的问题时,我将问题归结为以下几点(如本期所示:)
输入import TypeVar
从输入扩展导入协议
R=TypeVar(“R”,协变=True)
S=TypeVar(“S”,逆变=True)
类ArityOne(协议[S,R]):
定义调用(self,s:s)->R:
...
定义id_f(x:ArityOne[S,R])->ArityOne[S,R]:
返回x
X=类型变量(“X”)
def标识(x:x)->x:
返回x
i:int=id\u f(identity)(4)#不进行类型检查,应为'X'类型,获取的类型为'Literal[4]`
注意差距,这是一个最小的可复制的缺失链接的例子
我最初尝试做的是以下几点(跳过实际的curry
实现,相比之下,这类似于在公园里散步):
curry
decorator(不带类型)协议
s,这是函数类型可调用
的更现代版本。巧合的是,协议
s可以为它们的\uuuuu调用
方法指定类型@重载
,这就引出了下一点协议
s和类型@重载
ed\u调用
方法定义当前二进制、当前二进制(等)curry
函数定义类型@重载
s,例如二进制->CurriedBinary
,三元->curriedbternary
int->int
或str->int->bool
,它工作得非常好。
我现在手头上没有我尝试的实现
但是,当对诸如map
或filter
之类的函数进行curry时,它无法将函数的curry通用版本与实际类型相匹配
讨价还价
这是由于类型变量的作用域是如何工作的。有关更多详细信息,您可以查看GitHub问题。这里有更详细的解释
本质上,要处理的泛型函数的类型变量不受要部分传递的数据的实际类型的影响,因为在这两者之间有一个类协议,它定义了自己的类型变量范围
试图包装或重组这些东西并没有产生丰硕的成果
抑郁
我使用Protocol
s来表示当前函数的类型,
例如,Callable
,这是不可能的,尽管pyright
将重载函数的类型显示为重载[contract1,contract2,…]
但没有这样的符号,只有@Overload
因此,无论哪种方式,都有一些东西阻止你表达你想要的类型
由于python类型系统的限制,目前无法表示完全通用的typesafe curry函数
接受
但是,在某些特性上,如泛型或输入参数的任意分区,可能会有所妥协
下面的curry
实现在pyright 1.1.128
中工作
from typing import TypeVar, Callable, List, Optional, Union
R = TypeVar("R", covariant=True)
S = TypeVar("S", contravariant=True)
T = TypeVar("T", contravariant=True)
def curry(f: Callable[[T, S], R]) -> Callable[[T], Callable[[S], R]]:
raise Exception()
X = TypeVar("X")
Y = TypeVar("Y")
def function(x: X, y: X) -> X:
raise Exception()
def to_optional(x: X) -> Optional[X]:
raise Exception()
def map(f: Callable[[X], Y], xs: List[X]) -> List[Y]:
raise Exception()
i: int = curry(function)(4)(5)
s: List[Optional[Union[str, int]]] = curry(map)(to_optional)(["dennis", 4])
一个没有答案,另一个。你使用的是什么版本的mypy?您的代码在我的0.740上运行良好。我使用了mypy 0.761。谢谢您的回答。我就是这么做的。只为一个、三个、四个参数添加了重载。