Python为什么不允许多行lambda?


50

有人可以解释BDFL选择使Python Lambdas单行的具体原因吗?

这很好:

lambda x: x**x

这会导致错误:

lambda x:
    x**x

我了解到,使lambda多行处理会以某种方式“干扰”正常的缩进规则,并且需要添加更多的例外,但这不值得吗?

以JavaScript为例。没有这些匿名功能,怎能生存?它们是必不可少的。难道Pythonista使用者不希望为了将每个多行函数都作为参数传递而不得不命名吗?


3
考虑到您注意到了Guido不允许多重表达lambda并随后将其消除的具体原因,我假设您正在寻求验证,而不是真正的答案。
杰森·贝克

3
除了保存七个字符外,这还比a更好def吗?现在,它具有完全相同的视觉结构。
2011年

Answers:


42

吉多·范·范·罗苏姆(Guido van van Rossum)自己回答:

但是,此类解决方案通常缺少“ Pythonicity”(Python的良好功能),即良好的Python功能难以捉摸的特征。很难将Pythonic表示为硬约束。甚至Python的Zen也不能转化为对Pythonicity的简单测试...

在上面的示例中,很容易找到所提出的解决方案的致命弱点:双冒号虽然在语法上确实是明确的(“难题约束”之一),但完全是任意的,并且与Python中的任何其他事物都不相似...

但是我也拒绝这样做,因为最后(这是我无意间误导提交者的地方),我发现任何无法接受的解决方案都将基于缩进的块嵌入表达式的中间。由于我发现语句分组的替代语法(例如花括号或begin / end关键字)同样不可接受,因此,这几乎使多行lambda成为无法解决的难题。

http://www.artima.com/weblogs/viewpost.jsp?thread=147358

他说,基本上,尽管有解决方案是可行的,但与Python的方式并不一致。


2
+1感谢您的线程链接-但我仍然会感谢多行lambdas-它们非常宝贵-看看JavaScript,PHP也包括了它们。
树编码器

2
@greengit您可以只使用嵌套的def。它与匿名函数不同,但是它们足够接近。
jsternberg 2011年

2
当将函数作为参数传递时,嵌套的def并没有帮助-这就是为什么我想要多行lambds的第一原因
treecoder

1
@greengit-我认为,与在此处发表评论相比,最好使用GvR。
杰森·贝克

9
@greengit:您知道您可以将函数作为参数传递给另一个函数吗?您不能内联编写它,但是没有不可用的编程技术。
btilly 2011年

24

在python中执行多行lambda是完全可以的:请参阅

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

真正的lambda限制是lambda必须是单个表达式的事实;它不能包含关键字(例如python2 printreturn)。

GvR选择这样做是为了限制lambda的大小,因为它们通常用作参数。如果您想使用真正的功能,请使用def


1
是关于'\ n'字符的插入:D python没有多语句 lambda。您真的想使用def。想一想:您真的需要将callable作为函数的参数吗?并且不允许该函数的用户传递您的默认可调用对象?如果您不给他们,他们怎么能通过它?
维托·德·图利奥

顺便说一句,您能举例说明您需要一个匿名函数吗?
维托·德·图利奥

1
是的,我发现单个表达式的局限性令人沮丧。的确,如果他们允许多重表达的lambda,人们肯定会开始滥用它,但反之则是过于严格的杂项。
rbaleksandar '16

10

我知道这已经很老了,但在这里作为参考。

使用lambda的替代方法可以是使用def非常规方式。目标是将a传递def给函数,该函数只能在一种情况下完成-装饰器。请注意,使用此实现def result不会创建函数,而是会创建结果reduce(),最终结果是dict

无耻的插头:我在这里做很多事情

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

请注意,多语句lambda 可以完成,但只能使用非常丑陋的代码。但是,有趣的是作用域如何与此实现一起工作(请注意name变量的多次使用和变量的阴影message

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!

+1代表
单调

Monad在JavaScript BTW中也称为thenables或future / promises甚至回调。
aoeu256

3

一起编写多语句lambda并不像pyrospade所表明的那样糟糕:确保我们可以使用bind组成一堆monadic 函数,就像在Haskell中一样,但是由于我们处在Python的不纯净世界中,所以我们也可以使用副作用来实现相同的目的。

我在博客上介绍了几种方法。

例如,Python保证按顺序评估元组的元素,因此我们可以,像命令式一样使用;。我们可以将许多语句(如)替换print为表达式(如)sys.stdout.write

因此,以下内容是等效的:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

请注意,我None在末尾添加了a ,并使用提取了它[-1]。这将显式设置返回值。我们不必这样做,但是如果没有它,我们将得到一个时髦的(None, None, None)返回值,我们可能会或可能不会在乎。

因此,我们可以对IO操作进行排序。局部变量呢?

Python的=形式是一条语句,因此我们需要找到一个等效的表达式。一种方法是更改​​作为参数传入的数据结构的内容。例如:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

在中使用了一些技巧stateful_lambda

  • *_参数允许我们的lambda接受任意数量的参数。由于这允许参数,因此我们恢复的调用约定stateful_def
    • 调用参数_只是一个约定,上面写着“我不会使用此变量”
  • 我们有一个函数(“包装器”)返回了另一个函数(“ main”): lambda state: lambda *_: ...
    • 由于词汇作用域,第一个函数的参数将在第二个函数的范围内
    • 现在接受一些参数并返回另一个函数以稍后接受其余参数被称为currying
  • 我们立即调用“包装器”函数,并向其传递一个空字典: (lambda state: ...)({})
    • 这使我们无需使用赋值语句即可将变量分配给state{}(例如state = {}
  • 我们将键和值state视为变量名和绑定值
    • 与使用立即调用的lambda相比,此方法不那么麻烦
    • 这使我们能够改变变量的值
    • 我们用state.setdefault(a, b)代替a = bstate.get(a)代替a
  • 我们使用元组将我们的副作用链接在一起,就像以前一样
  • 我们[-1]用来提取最后一个值,就像return声明一样

当然这很麻烦,但是我们可以使用辅助函数来制作更好的API:

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the `get` and `setdefault` methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh `get` and `setdefault`
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses `get` and `set`(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])

您在开玩笑吗,这看起来像一场噩梦大声笑
Alexander Mills

1
@AlexanderMills嘿,这是不打算作为一个真实的例子,更pyrospade的lambda表达式,在-Lambda表达式,在-lambda表达式方法的反驳,证明事情并非坏的。实际上,现在有了python.org/dev/peps/pep-0572
Warbo,

1

我虽然可以提供帮助,但请使用断行器:

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

不要忘记python能够编写oneliners的好处,例如:

a=b=0; c=b+a; d = a+b**2 #etc etc

而且lambda非常强大,但是它并不意味着要替换1个完整函数,我的意思是您可以像hack一样(从上面的同事那里借用示例):

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

但是,您真的要这样吗?一段时间后,它几乎是不可读的,就像从未解开的末端开始到绳索的开头。 松散的绳索

Lambda被视为唯一的函数,在面向函数的编程中(除其他功能外)在map,filter和reduce函数中。例如,获取整数值且可被2整除的字符值

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = map( chrDev2, range(30) )
>>> ['\x00', 1, '\x02', 3, '\x04', 5, '\x06', 7, '\x08', 9, '\n', 11, '\x0c', 13, '\x0e', 15, '\x10', 17, '\x12', 19, '\x14', 21, '\x16', 23, '\x18', 25, '\x1a', 27, '\x1c', 29]

您可以通过定义复杂函数(或更多和几个lambda,并将其放在另一个lambda中)来将其用作函数expresions函数:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

但是Python以另一种方式提供了对函数表示的支持:-让我们说您调用了某个函数,superAwesomeFunction并且该函数可以执行一些非常出色的工作,您可以不调用它就将其分配给变量,如下所示:

SAF = superAwesomeFunction # there is no () at the end, 

因此,现在当您调用SAF时,将调用superAwesomeFunction或方法。如果您在Lib文件夹中搜索,您会发现大多数python __builtin__模块都是以这种方式编写的。这样做是因为有时您需要某些功能来执行特定任务,而这些功能对于用户来说并不是必需的,但是对于多个功能来说是必需的。因此,您可以选择不使用名称为“ superAwesomeFunction”的2个函数,可以使用“ superAwesomeFunctionDoingBasicStuf”和“ realSuperAwesomeFunction”,而不是仅将“ realSuperAwesomeFunction”放入“ superAwesomeFunction”变量中就可以了。

您可以通过在console中输入importedModule.__file__(实际示例import os;os.__file__)来找到导入模块的位置,只需跟随该目录访问名为importedModule.py的文件,然后在编辑器中将其打开,即可找到如何最大化自己的“知识”。

希望这对您和其他遇到麻烦的同事有所帮助。

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.