为啥 Python 的 `lambda` 表达式中不允许赋值?
Posted
技术标签:
【中文标题】为啥 Python 的 `lambda` 表达式中不允许赋值?【英文标题】:Why are assignments not allowed in Python's `lambda` expressions?为什么 Python 的 `lambda` 表达式中不允许赋值? 【发布时间】:2018-10-09 23:30:35 【问题描述】:这不是 Assignment inside lambda expression in Python 的副本,也就是说,我不是询问如何欺骗 Python 在 lambda
表达式中进行赋值。
我有一些 λ-演算背景。考虑以下代码,它
看起来 Python 很愿意在 lambda
中执行副作用
表达式:
#!/usr/bin/python
def applyTo42(f):
return f(42)
def double(x):
return x * 2
class ContainsVal:
def __init__(self, v):
self.v = v
def store(self, v):
self.v = v
def main():
print('== functional, no side effects')
print('-- print the double of 42')
print(applyTo42(double))
print('-- print 1000 more than 42')
print(applyTo42(lambda x: x + 1000))
print('-- print c\'s value instead of 42')
c = ContainsVal(23)
print(applyTo42(lambda x: c.v))
print('== not functional, side effects')
print('-- perform IO on 42')
applyTo42(lambda x: print(x))
print('-- set c\'s value to 42')
print(c.v)
applyTo42(lambda x: c.store(x))
print(c.v)
#print('== illegal, but why?')
#print(applyTo42(lambda x: c.v = 99))
if __name__ == '__main__':
main()
但是如果我取消注释这些行
print('== illegal, but why?')
print(applyTo42(lambda x: c.v = 99))
我会得到
SyntaxError: lambda cannot contain assignment
为什么不呢? 这背后的深层原因是什么?
正如代码所示,它不能是关于“纯度”的 功能意义。
我能想到的唯一解释是,assignemts 没有
返回任何东西,甚至不返回 None
。但这听起来很蹩脚而且会
易于修复(一种方法:使 lambda 表达式返回 None
if
body 是一个语句)。
不是答案:
因为它是这样定义的(我想知道为什么它是这样定义的)。
因为它在语法中(见上文)。
如果需要语句,请使用def
(我没有问如何获取
语句到函数中)。
“这会改变语法/语言/语义”如果你能想出一个这样的改变的例子,以及为什么它会不好的话,作为答案是可以的。
【问题讨论】:
因为lambdas
故意仅限于表达式。如果您想要所有其他内容,只需使用完整的函数定义。这是规格:docs.python.org/3/reference/expressions.html#lambda
将赋值设为语句而不是表达式的理由是,像if x = 3:
(而不是if x == 3:
)这样的拼写错误会变成语法错误,而不是细微的运行时语义错误。
看这个解释:effbot.org/pyfaq/why-can-t-lambda-forms-contain-statements.htm
您的特定示例存在漏洞:lambda x: setattr(c, 'v', 99)
。
以上 cmets 都对这个问题没有用处。有些人甚至可能没有完全阅读我的问题。
【参考方案1】:
有一个语法问题:赋值是语句,而lambda的主体只能有表达式。 Python 的语法就是这样设计的1。查看https://docs.python.org/3/reference/grammar.html。
还有一个语义问题:每条语句返回什么?
我不认为有兴趣改变它,因为 lambdas 用于非常简单和简短的代码。此外,一个语句也允许 sequences 语句,这对于 lambdas 来说是不可取的。
也可以通过选择性地允许 lambda 主体中的某些语句并指定语义来修复它(例如,赋值返回 None
,或返回分配的值;后者对我来说更有意义)。但是有什么好处呢?
Lambda 和函数是可以互换的。如果您在 lambda 主体中确实有特定语句的用例,您可以定义一个执行它的函数,您的具体问题就解决了。
也许您可以使用MacroPy3 创建一个语法宏来允许它(我只是猜测,因为我是该项目的粉丝,但我仍然没有时间深入研究它)。
例如,MacroPy 将允许您定义一个将 f[_ * _]
转换为 lambda a, b: a * b
的宏,因此为调用您定义的函数的 lambda 定义语法应该不是不可能的。
1 不改变它的一个很好的理由是它会削弱语法,因为 lambda 可以出现在表达式可以出现的地方。声明不应该。但这是我自己的一个非常主观的评论。
【讨论】:
我认为你的脚注不仅仅是一个主观的评论,它是lambda
的全部意义首先存在(并且没有在 3.0 中被删除)。
“Python 的语法就是这样设计的”——显然是这样,但为什么呢?否则会破坏什么?
“但是有什么好处呢?” — 传递一个只分配给累加器的回调:foobar(lambda x: acc = x)
。【参考方案2】:
就目前而言,Python 被设计为一种基于语句的语言。因此赋值和其他名称绑定都是语句,没有任何结果。
Python 核心开发人员目前正在讨论 PEP 572,它将引入 name-binding expression。
【讨论】:
PEP-572 在这里没有帮助,因为那里提出的名称绑定是 local;您将无法设置全局变量。【参考方案3】:lambda
存在的全部原因在于它是一个表达式。1如果您想要类似于 lambda
但又是一个语句的东西,那就是 def
。
Python 表达式不能包含语句。事实上,这是该语言的基础,Python 从这个决定中获得了很多好处。这是流控制缩进起作用的原因,而不是像许多其他尝试(如 CoffeeScript)那样笨拙。这就是您可以通过浏览每行中的第一个对象来读取状态更改的原因。这甚至是该语言易于解析的部分原因,对于编译器和人类读者来说都是如此。2
改变 Python 以通过某种方式“逃避”语句-表达式的鸿沟,除非以一种非常谨慎和有限的方式,否则它将变成一种完全不同的语言,并且不再具有导致人们首先选择 Python。
更改 Python 以生成大多数语句表达式(例如 Ruby)将再次将其变成完全不同的语言,而没有 Python 当前的优势。
如果 Python 确实 做出了这些更改中的任何一个,那么首先就没有理由使用 lambda
;2,3 你可以只需在表达式中使用 def
语句即可。
如果将 Python 改为使用赋值表达式呢?好吧,很明显这会破坏“您可以通过浏览每行中的第一个对象来读取状态更改”。尽管 Guido 通常关注if spam=eggs
是错误而不是有用的事实。
Python 确实为您提供了在需要时解决该问题的方法,例如 setattr
甚至在 globals()
上显式调用 __setitem__
,但这并不意味着它应该具有直接的语法支持。很少需要的东西不值得语法糖——更不用说那些不寻常的东西,当它实际完成时应该引起注意和/或危险信号。
1。我不知道 Guido 最初在 Python 1.0 中添加 lambda
时是否是这样理解的。但这绝对是 Python 3.0 中没有删除 lambda
的原因。
2。事实上,Guido 曾多次表示,允许人类可以在头脑中运行的 LL(1) 解析器是该语言基于语句的充分理由,以至于其他好处甚至不需要讨论. I wrote about this a few years ago如果有人感兴趣的话。
3。如果您想知道为什么这么多语言确实有lambda
表达式,尽管已经有def
:在许多语言中,从 C++ 到 Ruby,函数不是可以被传递,所以他们不得不发明第二个东西,它是一流的,但像一个函数一样工作。在其他情况下,从 Smalltalk 到 Java,函数甚至存在,只有方法,所以他们不得不再次发明第二个东西,它不是一种方法,而是像一个方法一样工作。 Python 没有这些问题。
4.一些语言,如 C# 和 javascript,实际上具有完美的内联函数定义,但添加了某种 lambda
语法作为纯语法糖,以使其更简洁和更少样板。这实际上可能在 Python 中值得做(尽管到目前为止,对良好语法的每一次尝试都失败了),但它不会是当前的 lambda
语法,它几乎和 def
一样冗长。
【讨论】:
我觉得你想告诉我一些我还不明白的事情。也许您可以详细说明如果 lambda 表达式中允许使用语句(例如,返回None
),该语言将如何发生巨大变化甚至中断。
“你可以通过浏览每行中的第一个对象来读取状态变化”——不是真的。这意味着每一行中只有一个对象可以改变状态。
@stefan 在惯用的 Python 中确实如此:在绝大多数语句中,只有 =
左边的东西在赋值语句中被赋值,或者在表达式语句中的第一个对象有一个单一的变异操作。当然,如果您愿意,您可以通过编写一个包含十几个 setattr
调用的表达式来违反这一点。或者你可以用分号将九个语句放在一大行中。 Python 并没有让编写难以理解的代码变得不可能;它只是让编写不难理解的代码变得容易,并鼓励通过固执己见的习语来这样做。
fileHandle.write(stack.pop())
难以跟上?但是有两个状态变化。【参考方案4】:
我的回答基于上面的chepner's comment,并没有来自任何其他可信或官方来源,但我认为它会很有用。
如果在 lambda 表达式中允许赋值,那么混淆 ==
(平等测试)和 =
(赋值)的错误将有更多机会逃逸。
例子:
>>> # Correct use of equality test
... list(filter(lambda x: x==1, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[1, 1.0, (1+0j)]
>>> # Suppose that assignment is used by mistake instead of equality testing
... # and the return value of an assignment expression is always None
... list(filter(lambda x: None, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[]
>>> # Suppose that assignment is used by mistake instead of equality testing
... # and the return value of an assignment expression is the assigned value
... list(filter(lambda x: 1, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[0, 1, 0.0, 1.0, 0j, (1+0j)]
【讨论】:
【参考方案5】:其实并没有什么更深层次的原因,跟 lambda 或者函数式语言设计无关,只是为了避免程序员混用 = 和 == 运算符,这是其他语言中很常见的错误
如果这个故事还有更多内容,我假设可能是因为 python bdfl GVR 表达了他对 lambda 和其他功能特性的不喜欢的一面,并尝试(并承认)将它们从 python 3 中完全删除https://www.artima.com/weblogs/viewpost.jsp?thread=98196
在撰写本文时,核心开发人员最近就是否包含有限名称绑定表达式分配进行了激烈讨论,争论仍在继续,所以也许有一天我们可能会在 lambda 中看到它(不太可能)
正如你自己所说,这绝对不是副作用或纯度,他们只是不希望 lambda 不仅仅是一个表达式...... ... ...
话虽如此,这里有一些关于 lambda 中的多表达式分配的内容,如果您感兴趣,请继续阅读
这在 python 中并非不可能,事实上有时需要通过(ab)使用 kwargs(关键字参数)来捕获变量和回避后期绑定
编辑:
代码示例
f = lambda x,a=1: (lambda c = a+2, b = a+1: (lambda e = x,d = c+1: print(a,b,c,d,e))())()
f("w")
# output 1 2 3 4 w
# expression assignment through an object's method call
if let(a=1) .a > 0 and let(b=let.a+1) .b != 1 and let(c=let.b+let.a) .c:
print(let.a, let.b, let.c)
# output 1 2 3
【讨论】:
【参考方案6】:我想所有的人都已经回答了这个问题。当我们只想这样做时,我们主要使用 lambdas 函数:
-创建一些在特定位置完美完成工作的简单函数(大部分时间隐藏在其他一些大函数中 - lambda 函数没有名称 - 可以与其他一些内置函数一起使用,例如地图、列表等...
>>> Celsius = [39.2, 36.5, 37.3, 37.8]
>>> Fahrenheit = map(lambda x: (float(9)/5)*x + 32, Celsius) # mapping the list here
>>> print Fahrenheit
[102.56, 97.700000000000003, 99.140000000000001, 100.03999999999999]
请访问此网页,这可能很有用。继续加油!!! https://www.python-course.eu/lambda.php
【讨论】:
【参考方案7】:只要lambda
内部允许exec()
(和eval()
),你就可以在lambda
内部做赋值:
q = 3
def assign(var_str, val_str):
exec("global " + var_str + "; " +
var_str + " = " + val_str)
lambda_assign = lambda var_str, val_str: assign(var_str, val_str)
q ## gives: 3
lambda_assign("q", "100")
q ## gives: 100
## what would such expression be a win over the direct:
q = 100
## ? `lambda_assign("q", "100")` will be for sure slower than
## `q = 100` isn't it?
q_assign = lambda v: assign("q", v)
q_assign("33")
q ## 33
## but do I need lambda for q_assign?
def q_assign(v): assign("q", v)
## would do it, too, isn't it?
但是由于 lambda 表达式只允许在其主体内定义 1 个表达式(至少在 Python 中......),所以允许在 lambda 内进行赋值有什么意义呢?它的最终效果是直接分配(不使用任何 lambda)q = 100
,不是吗?
这会比在定义的 lambda 上执行它更快,因为您至少有一个函数查找和执行更少的执行......
【讨论】:
在触发信号时设置变量可能很有用。我正在使用 pyQt,当用户更改 UI 中的某些内容时,我会更改运行时变量以上是关于为啥 Python 的 `lambda` 表达式中不允许赋值?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在未计算的操作数中不允许使用 lambda 表达式,但在常量表达式的未计算部分中允许使用 lambda 表达式?