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


83

当前,在Python中,可以如下提示类型的函数的参数和返回类型:

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

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

但是,此语法与lambda高度混淆,如下所示:

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

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

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


我认为lambda的通常用例嵌套在另一个函数中(与在一个地方定义并在完全不同的地方调用的常规函数​​相反)。如果您已经知道该函数中的类型,那么(原则上),应该很容易找出lambda将要接收的类型。另外,修改语法以支持注释只会使您的lambda难以阅读。
mgilson

你为什么想这么做?lambda语法用于“抛弃式”函数,这些函数可在非常受限的上下文中使用,例如,作为内置key参数sorted。我真的看不到在如此有限的上下文中添加类型提示的意义。此外,使用PEP-526变量注释添加类型提示以lambda完全忽略要点,恕我直言。该lambda语法旨在定义匿名函数。使用lambda并立即将其绑定到变量有什么意义?只需使用def
Luciano Ramalho

Answers:


90

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

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协议__call__;如果您需要明确表示应接受哪些参数,请使用此选项。您需要Python 3.8为反向移植安装typing-extensions项目

from typing-extensions import Protocol

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

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

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

PEP 3107-功能注释

lambda的语法不支持注释。lambda的语法可以更改为支持注释,方法是在参数列表周围要求使用括号。但是,由于以下原因,决定不进行此更改:

  • 这将是不兼容的更改。
  • 无论如何,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'>}

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


1
如果答案是,func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)那么为什么不是更好的答案呢def func(var1: str, var2: str) -> int: return var1.index(var2)
Guido van Rossum

@GuidovanRossum:因为这是另一个问题的答案。:-)这里的问题是如何将类型提示应用于具有与定义函数相同结果的lambda。不过,我将添加一个部分,介绍为什么您可能不想使用lambda。
马丁·彼得斯

1
不,从字面上看,答案应该是“不,不可能,没有计划对此进行更改,原因主要是语法上的-定义命名函数并对其进行注释很容易。” 我还注意到,如果lambda埋在大型调用或数据结构中,则建议的解决方法(创建具有特定Callable类型的变量)不是很有用,因此在任何意义上都比定义一个函数“更好”。(我认为这很糟糕,因为Callable表示法不是很容易理解。)
Guido van Rossum

该解决方案还分散了注意力,就像它正在定义具有某种Callable类型的变量一样,该变量在其生命周期内可以分配不同的lambda。这与定义一个函数(如果您重新定义或重新定义一个def而不是Callable类型的变量,至少要使mypy讨厌)不同,而与最初的问题“我只想要带有类型注释的lambda”不同。我继续声称,在这种情况下,保留lambda形式而不是def形式没有任何好处。(即使没有类型注释,我也要说。)
Guido van Rossum

1
@GuidovanRossum:那我可以邀请您写自己的答案吗?有了您作为语言之父的特殊地位,您给出的答案很容易超过给定的答案,并且您可以准确地写出自己应有的语言。
马丁·彼得斯

43

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

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

我不明白这个答案:您在哪里设置类型x
stenci

3
@stenciis_even函数是一个Callable需要一个int参数的a,因此x是一个int

我现在知道了。乍一看,我认为您只是在注释lambda返回的类型,而不是其参数的类型。
stenci

4
这实际上并不是对lambda本身的注释:您无法从lambda对象中检索这些注释,就像对带注释的函数一样
cz

3
使用python 3.7的mypy 0.701会正确地is_even('x')进行类型检查:导致类型错误。
康拉德·鲁道夫

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.