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

ste*_*fan 14 python lambda side-effects python-internals

这不是Python中lambda表达式中Assignment的重复,也就是说,我不会问如何欺骗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()
Run Code Online (Sandbox Code Playgroud)

但如果我取消注释线

    print('== illegal, but why?')
    print(applyTo42(lambda x: c.v = 99))
Run Code Online (Sandbox Code Playgroud)

我去拿

SyntaxError: lambda cannot contain assignment
Run Code Online (Sandbox Code Playgroud)

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

  • 正如代码所示,它不能是功能意义上的"纯度".

  • 我能想象的唯一解释是,assignemts不返回任何东西,甚至不返回None.但这听起来很蹩脚,并且很容易修复(一种方法:None如果body是一个语句,则使lambda表达式返回).

不是答案:

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

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

  • def如果需要语句,请使用(我没有询问如何将语句添加到函数中).

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

aba*_*ert 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(),这并不意味着它应该具有直接的语法支持.很少需要的东西不值得语法糖 - 甚至对于那些不寻常的东西更加如此,它应该在实际完成时引起眉毛和/或红旗.


我不知道Guido在最初添加lambda回Python 1.0 时是否理解这一点.但它绝对lambda是Python 3.0中没有删除的原因.

2.实际上,Guido多次提出允许人类可以在头脑中运行的LL(1)解析器是语言基于语句的充分理由,以至于其他好处甚至不需要讨论.几年前我写过这篇文章,如果有人感兴趣的话.

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

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

  • @stefan在惯用的Python中也是如此:在绝大多数语句中,只有`=`左边的东西在赋值语句中被赋值,或者表达式语句中的第一个对象只有一个变异操作.当然,如果你愿意,你可以通过编写一个充满十几个`setattr`调用的表达来违反它.或者你可以用分号在一行中放九个语句.Python不会让_impossible_编写难以理解的代码; 只是让它很容易编写出难以理解的代码,并鼓励通过固执的习语这样做. (3认同)

ffe*_*rri 6

存在语法问题:赋值是一个语句,而lambda的主体只能有表达式.Python的语法就是这样设计的1.请访问https://docs.python.org/3/reference/grammar.html查看.

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

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

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

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


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

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


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