是否可以输入提示 lambda 函数?

Posted

技术标签:

【中文标题】是否可以输入提示 lambda 函数?【英文标题】:Is it possible to type hint a lambda function? 【发布时间】:2016-02-23 08:46:41 【问题描述】:

目前,在 Python 中,函数的参数和返回类型可以按如下方式进行类型提示:

def func(var1: str, var2: str) -> int:
    return var1.index(var2)

这表示该函数接受两个字符串,并返回一个整数。

但是,这种语法与 lambdas 很容易混淆,它看起来像:

func = lambda var1, var2: var1.index(var2)

我尝试在参数和返回类型上都添加类型提示,但我想不出一种不会导致语法错误的方法。

是否可以键入提示 lambda 函数?如果没有,是否有类型提示 lambdas 的计划,或者任何原因(除了明显的语法冲突)为什么不呢?

【问题讨论】:

我认为 lambda 的常用用例嵌套在另一个函数中(与在一个地方定义并在完全不同的地方调用的常规函数​​形成对比)。如果您已经知道该函数中的类型,那么(原则上)应该很容易弄清楚 lambda 将接收哪些类型。此外,修改语法以支持注释只会使您的 lambda 更难阅读 您为什么要这样做? lambda 语法适用于在非常受限的上下文中使用的“丢弃”函数,例如,作为 sorted 内置参数的 key 参数。在如此有限的上下文中,我真的没有看到添加类型提示的意义。此外,使用 PEP-526 变量注释向lambda 添加类型提示完全没有抓住重点,恕我直言。 lambda 语法旨在定义 anonymous 函数。使用lambda 并立即将其绑定到变量有什么意义?只需使用def 【参考方案1】:

您可以在 Python 3.6 及更高版本中使用 PEP 526 variable annotations。您可以使用typing.Callable generic 注释您分配lambda 结果的变量:

from typing import Callable

func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)

这不会将类型提示信息附加到函数对象本身,仅附加到您存储对象的命名空间,但这通常是类型提示所需的全部内容。

但是,您也可以只使用函数语句; lambda 提供的唯一优势是您可以将简单表达式的函数定义放入更大的表达式中。但是上面的 lambda 不是更大表达式的一部分,它只是赋值语句的一部分,将它绑定到一个名称。这正是def func(var1: str, var2: str): return var1.index(var2) 语句所能达到的效果。

请注意,您也不能单独注释 *args**kwargs 参数,正如 Callable 的文档所述:

没有语法来指示可选或关键字参数;此类函数类型很少用作回调类型。

该限制不适用于PEP 544 protocol with a __call__ method;如果您需要对应该接受哪些参数进行表达定义,请使用它。您需要 Python 3.8 安装 typing-extensions project 以获得反向端口:

from typing_extensions import Protocol

class SomeCallableConvention(Protocol):
    def __call__(self, var1: str, var2: str, spam: str = "ham") -> int:
        ...

func: SomeCallableConvention = lambda var1, var2, spam="ham": var1.index(var2) * spam

对于lambda 表达式本身,您不能使用任何注释(Python 类型提示所基于的语法)。该语法仅适用于 def 函数语句。

来自PEP 3107 - Function Annotations

lambda 的语法不支持注解。可以通过在参数列表周围使用括号来更改 lambda 的语法以支持注释。但是was decided 不进行此更改,因为:

这将是一个不兼容的更改。 Lambda 无论如何都已绝育。 始终可以将 lambda 更改为函数。

您仍然可以将注释直接附加到对象,function.__annotations__ 属性是可写字典:

>>> def func(var1: str, var2: str) -> int:
...     return var1.index(var2)
...
>>> func.__annotations__
'var1': <class 'str'>, 'return': <class 'int'>, 'var2': <class 'str'>
>>> lfunc = lambda var1, var2: var1.index(var2)
>>> lfunc.__annotations__

>>> lfunc.__annotations__['var1'] = str
>>> lfunc.__annotations__['var2'] = str
>>> lfunc.__annotations__['return'] = int
>>> lfunc.__annotations__
'var1': <class 'str'>, 'return': <class 'int'>, 'var2': <class 'str'>

当然,当您想对类型提示运行静态分析器时,像这样的动态注释不会对您有所帮助。

【讨论】:

如果答案是func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2) 那么为什么不是更好 答案def func(var1: str, var2: str) -&gt; int: return var1.index(var2)??? @GuidovanRossum:因为这是一个不同问题的答案。 :-) 这里的问题是如何将类型提示应用于给出与定义函数相同的结果的 lambda。不过,我将添加一个部分,说明您可能不想使用 lambda 的原因。 不,从字面上看,答案应该是“不,不可能,没有计划改变这一点,原因主要是语法上的——定义一个命名函数很容易,并且注释那个。”我还注意到,如果 lambda 隐藏在大型调用或数据结构中,建议的解决方法(创建具有特定 Callable 类型的变量)不是很有帮助,因此它在任何意义上都没有比定义函数“更好”。 (我认为它是 worse 因为 Callable 表示法不是很可读。) @GuidovanRossum:那我可以邀请你自己写答案吗?鉴于您作为语言之父的特殊地位,在给定时间的情况下,您的答案很容易超过这个答案,并且您可以准确地写出您认为应该在其中的内容。 为了定义一个类型化的函数变量,以及一个默认值(可能是 noop),我认为上面的答案是合理的(尽管由于 Callable 语法而丑陋)代码【参考方案2】:

从 Python 3.6 开始,您可以(参见 PEP 526):

from typing import Callable
is_even: Callable[[int], bool] = lambda x: (x % 2 == 0)

【讨论】:

这个答案我看不懂:x的类型在哪里设置? @stenci is_even 函数是一个 Callable,它需要一个 int 参数,所以 x 是一个 int 我现在明白了。乍一看,我以为您只是在注释 lambda 返回的类型,而不是其参数的类型。 这实际上并没有对 lambda 本身进行注释:您不能从 lambda 对象中检索这些注释,因为您可以从带注释的函数中获取这些注释 mypy 0.701 与 Python 3.7 正确类型检查:is_even('x') 导致类型错误。【参考方案3】:

对于那些只想在编写代码时快速访问智能感知的人来说,一个几乎破解方法是在 lambda 声明之前对参数进行类型注释,完成你的工作并且只然后用参数遮蔽它。

    x: YourClass
    map(lambda _ :  x.somemethod ...) # x has access to methods defined on YourClass

然后,紧接着:

    x: YourClass # can remove or leave
    map(lambda x:  x.somemethod, ListOfYourObjects) # inner x now shadows the argument

【讨论】:

以上是关于是否可以输入提示 lambda 函数?的主要内容,如果未能解决你的问题,请参考以下文章

8086汇编函数递归求解Hanoi(汉诺塔)问题(有提示信息,判断输入是否有效,推荐输入范围:1~23)

8086汇编函数递归求解Hanoi(汉诺塔)问题(有提示信息,判断输入是否有效,推荐输入范围:1~23)

Typehinting lambda 函数作为函数参数

为啥用于 stl 函数的 lambda 函数,如 C++ 中的 sort() 、 max_element() 函数需要两个参数作为输入?

Python:lambda表达式的两种应用场景

匿名函数