为什么在Python的`lambda`表达式中不允许赋值?

时间:2018-04-29 20:14:38

标签: python lambda python-internals side-effects

这不是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

为什么不呢? 这背后的深层原因是什么?

  • 正如代码所示,它不能是关于“纯度”的 功能感。

  • 我能想象的唯一解释是,受助人没有 返回任何东西,甚至不归None。但这听起来很蹩脚而且会 很容易修复(一种方法:使lambda表达式返回None if 身体是一个声明)。

不是答案:

  • 因为它是这样定义的(我想知道它为什么这样定义)。

  • 因为它在语法中(见上文)。

  • 如果您需要陈述,请使用def(我没有询问如何获取 声明成功能)。

“这会改变语法/语言/语义”如果能够提出这样一个变化的例子,以及它为什么会变坏,那么它就可以作为答案。

7 个答案:

答案 0 :(得分:15)

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或甚至在__setitem__上明确调用globals(),但这并不代表它'应该有直接句法支持的东西。一些很少需要的东西并不值得语法糖 - 甚至对于一些非常不寻常的东西来说它应该在实际完成时引起眉毛和/或红旗。

<子> 1。当他最初在Python 1.0中添加lambda时,我不知道这是否是Guido的理解。但这绝对是Python 3.0中没有删除lambda的原因。

<子> 2。事实上,Guido多次提出允许人类可以在头脑中运行的LL(1)解析器是语言基于语句的充分理由,以至于其他好处甚至不需要讨论。 I wrote about this a few years ago如果有人感兴趣的话。

<子> 3。如果您想知道为什么这么多语言有一个lambda表达式,尽管已经有def:在许多语言中,从C ++到Ruby,函数不是&#39 ; t可以传递的第一类对象,因此他们必须发明第二类,它是一流的,但就像一个函数。在其他方面,从Smalltalk到Java,函数甚至不存在,只有方法,所以再一次,他们必须发明第二个不是方法而是像一个方法一样的东西。 Python没有这些问题。

<子> 4。一些语言,如C#和JavaScript,实际上具有完美的内联函数定义,但添加了某种lambda语法作为纯语法糖,使其更简洁,更少样板。这可能实际上值得在Python中进行(虽然到目前为止,每次尝试使用良好的语法都会失败),但它不会是当前的lambda语法,它几乎和{{1}一样冗长。 }。

答案 1 :(得分:6)

存在语法问题:赋值是语句,lambda的主体只能有表达式。 Python的语法以 1 的方式设计。请查看https://docs.python.org/3/reference/grammar.html

还有一个语义问题:每个语句返回什么

我认为没有兴趣改变它,因为lambda是用于非常简单和简短的代码。此外,语句也允许序列语句,这对lambdas来说是不可取的。

也可以通过选择性地允许lambda体中的某些语句,并指定语义(例如赋值返回None,或返回指定的值;后者对我更有意义)来解决。但是有什么好处呢?

Lambda和函数是可以互换的。如果你真的有一个lambda体中特定语句的用例,你可以定义一个执行它的函数,并解决你的具体问题。

也许你可以创建一个语法宏来允许MacroPy3(我只是猜测,因为我是该项目的粉丝,但我仍然没有时间潜入它)

例如,MacroPy允许您定义一个将f[_ * _]转换为lambda a, b: a * b的宏,因此定义调用您定义的函数的lambda的语法不应该是不可能的。

1 不改变它的一个很好的理由是它会削弱语法,因为lambda可以在表达式可以的位置。声明不应该。但这是我自己的主观评论。

答案 2 :(得分:2)

我的回答基于上面的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)]

答案 3 :(得分:2)

只要在exec()内允许eval()(和lambda),就可以在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表达式只允许在其体内定义一个表达式(至少在Python中......),允许在lambda中进行赋值的意义是什么?它的净效果是直接分配(不使用任何lambda)q = 100,不是吗?

它比在定义的lambda上执行它更快,因为你至少有一个函数查找和执行少于执行......

答案 4 :(得分:1)

没有更深层次的原因,它与lambda或函数式语言设计无关,它只是为了避免程序员混合使用=和==运算符,这是一个非常常见的错误。其他语言

如果还有更多这个故事,我认为像MAYBE一样,因为python bdfl GVR表达了他对lambda和其他功能特性的不爱之处,并试图(并承认)将它们从python 3中删除{{3} }

在撰写本文时,核心开发人员最近就是否包含有限名称绑定表达式分配进行了激烈的讨论,争论仍在继续,所以也许有一天我们可能会在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

答案 5 :(得分:0)

目前,Python被设计为基于语句的语言。因此赋值和其他名称绑定是语句,并且没有任何结果。

Python核心开发人员目前正在讨论PEP 572,它将引入name-binding expression

答案 6 :(得分:0)

我想所有的研究员已经回答了这个问题。当我们想要:

时,我们主要使用lambdas函数

- 创建一些在特定位置完美工作的简单函数(大多数时候隐藏在其他一些大函数中) - lambda函数没有名称 - 可以与其他一些内置函数一起使用,例如map,list等......

>>> 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]

请访问此网页,这可能很有用。保持它! Blending functions of CatmullRom