Python中没有Multiline Lambda:为什么不呢?

Ima*_*ist 299 python syntax lambda

我听说它说多行lambda不能在Python中添加,因为它们会在语法上与Python中的其他语法结构发生冲突.我今天在公共汽车上考虑这个问题,并意识到我想不出多线lambdas碰撞的单个Python构造.鉴于我非常了解这门语言,这让我感到惊讶.

现在,我确定Guido有理由不在语言中包含多行lambdas,但是出于好奇:包含多行lambda的情况会有多么模糊?我听到的是真的,还是有其他原因使Python不允许多行lambda?

Eli*_*ght 596

Guido van Rossum(Python的发明者)在一篇旧博客文章中自己回答了这个问题.
基本上,他承认这在理论上是可行的,但任何提议的解决方案都是非Pythonic:

"但是对于这个难题,任何提出的解决方案的复杂性对我来说都是巨大的:它需要解析器(或更确切地说,词法分析器)能够在缩进敏感和缩进不敏感模式之间来回切换,保持堆栈以前的模式和缩进级别.从技术上讲,这一切都可以解决(已经有一堆可以推广的缩进级别.)但这些都没有消除我的直觉,认为这是一个精心设计的Rube Goldberg装置."

  • 为什么这不是最佳答案?这不是技术原因,它是设计选择,正如发明人明确指出的那样. (104认同)
  • Guido的答案只是我希望Python不依赖于缩进来定义块的另一个原因. (50认同)
  • 我不确定我会把"直觉"称为设计选择.;) (22认同)
  • @DanAbramov因为OP可能多年没有登录. (12认同)
  • 我还没有开始使用 PyTrain,但我一直在考虑它。仅这一件事,我必须用实际名称定义一个实际函数,以便将其用作更高阶的函数,就让我想吐,让我不想写 Python。“非Pythonic”?杀了我。 (9认同)
  • 对于那些不了解Rube Goldberg参考的人,请参阅:http://en.wikipedia.org/wiki/Rube_Goldberg_Machine (7认同)
  • Guido 的这种偏好是该语言中一些严重弊病的根源。我希望数据科学使用不同的语言(不,我_不_建议_java_..)。但我们到了。 (7认同)
  • 尽管有缩进语法,Haskell、Idris、CoffeeScript 仍具有多行 lambda... (4认同)
  • @ leo-the-manic因为“设计”一词意味着一定程度的故意和关怀。许多语言确实提供多行lambda,它们在设计中是非常有意的一部分。Python的缺乏实际上具有深远的意义,并使表面语法显着复杂化。(例如,装饰器和上下文管理器都可以被多行lambda消除。)因此,在存在大量相反的智慧的情况下,这种“直觉”并不是特别令人满意。除了使解析器复杂之外,它还使曾经编写的每个Python程序都变得复杂。 (3认同)
  • @ElliotCameron,您不同意设计决定,但这并不等于设计决定。来自直觉的事实也不是。同样,您断言选择复杂的Python程序完全是主观的……其他人则认为这使它们变得简单 (3认同)
  • 丹·阿布拉莫夫? (3认同)
  • 我不明白 Guido 的理由是如何必然必须排除使用分号的,如 `(lambda x: a=3; b=a*x; b**2)`...我确实理解它,如果这只是一个任意的设计选择,就像它看起来的那样 (3认同)
  • 在英国,相当于'Heath Robinson'. (2认同)
  • Ruby 可以用不同的方式定义作用域。 (2认同)
  • @ElliotCameron为什么不呢?这种直觉导致选择从Python设计中排除多行lambda。许多设计选择最终都归结为“直觉”或直觉。 (2认同)
  • @ leo-the-manic哈哈当然是主观的,就像van Rossom感觉多行lambda是“非Pythonic的”(即“ un-van-Rossom-like”)一样。Python的受欢迎程度丝毫没有影响。没有此功能,它将继续存在。 (2认同)

bal*_*pha 141

请看以下内容:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])
Run Code Online (Sandbox Code Playgroud)

这是一个lambda返回(y, [1,2,3])(因此map只获取一个参数,导致错误)?还是会回归y?或者它是语法错误,因为新行上的逗号是错误的?Python如何知道你想要什么?

在parens中,缩进与python无关,因此您无法明确地使用多行.

这只是一个简单的例子,可能还有更多例子.

  • 如果你想从lambda返回一个元组,他们可以强制使用括号.国际海事组织,这应该始终是强制执行,以防止这种模糊,但哦,好吧. (95认同)
  • 这是一个简单的歧义,必须通过添加一组额外的parens来解决,这些parens已存在于许多地方,例如由其他参数包围的生成器表达式,在整数字面上调用方法(尽管不需要这样的情况,因为a函数名不能以数字开头,当然还有单行lambda(可能是多行写的长表达式).多线lambda与这些案件并没有特别的不同,它保证在此基础上排除它们.[这](http://stackoverflow.com/a/1233520/933416)是真正的答案. (23认同)
  • 我喜欢有无数种语言可以毫无顾虑地处理它,但不知何故,有一些深刻的原因为什么它被认为是非常困难的,如果不是不可能的话 (13认同)
  • @nicolas 简而言之,这就是 python (4认同)
  • 对于函数式编程,我随时都会选择 Scala 而不是 Python。 (3认同)

Seb*_*tos 49

这通常非常难看(但有时替代方案甚至更难看),因此解决方法是制作括号表达式:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())
Run Code Online (Sandbox Code Playgroud)

它不会接受任何作业,所以你必须事先准备好数据.我发现这个有用的地方是PySide包装器,你有时会有短回调.编写额外的成员函数会更加难看.通常你不需要这个.

例:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
Run Code Online (Sandbox Code Playgroud)

  • 我的老板只是在我们的PyQt应用程序中要求这样的东西.真棒! (2认同)

Ano*_*non 18

几个相关链接:

有一段时间,我正在关注Reia的开发,它最初将使用Python的基于缩进的语法和Ruby块,所有这些都在Erlang之上.但是,设计师最终放弃了对缩进的敏感性,他写这篇关于该决定的帖子包括讨论他遇到的缩进+多行块问题,以及他对Guido的设计问题/决定的认识增加:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

此外,这里有一个有趣的Ruby内容块的提议我跑过Guido发布的响应没有实际拍摄它(不知道是否有任何后续的击落):

http://tav.espians.com/ruby-style-blocks-in-python.html


Sam*_*rif 10

[编辑]阅读此答案.它解释了为什么多线lambda不是一个东西.

简单地说,它是unpythonic.来自Guido van Rossum的博文:

我发现任何解决方案都无法接受,它会在表达式的中间嵌入基于缩进的块.由于我发现语句分组的替代语法(例如大括号或开始/结束关键字)同样不可接受,这几乎使得多行lambda成为无法解决的难题.

至于其余的答案.使用单行1 lambda或命名函数.请不要使用__CODE__- 我很遗憾曾经建议过.

1你会惊讶于你可以用一行python做什么.


获取多行lambda函数的解决方法(skriticos答案的扩展):

__PRE__

它能做什么:

  • Python在读取分隔符之前简化(执行)元组的每个组件.

  • 例如,__CODE__ 即使所使用的唯一信息是列表中的最后一项(0),也会执行所有三个函数.

  • 通常你不能在python中的列表或元组中分配或声明变量,但是使用__CODE__你可以使用的函数(注意它总是返回:) __CODE__.

  • 请注意,除非您声明一个变量,因为__CODE__它不会存在于该__CODE__函数调用之外(这仅适用__CODE____CODE__语句中的函数).

  • 例如,__CODE__没有__CODE__声明就可以正常工作 但是,__CODE____CODE__不.

  • 请注意,所有__CODE__变量都存储在全局命名空间中,并在函数调用完成后继续存在.因此,这不是一个好的解决方案,如果可能的话应该避免. __CODE____CODE__lambda函数内的函数声明的变量与__CODE__命名空间保持独立.(在Python 3.3.3中测试)

  • __CODE__在元组的最后得到的最后一个索引.例如__CODE____CODE__.这样做以便仅所希望的输出值(一个或多个)被返回,而不是包含整个元组__CODE____CODE__功能和其他外来的值.

等效多线功能:

__PRE__

避免需要多行lambda的方法:

递归:

__PRE__

布尔是整数:

__PRE__

迭代器:

__PRE__

  • 有没有办法不止一次地讨厌答案? (13认同)
  • "荣耀但可怕的黑客"也适用于此. (2认同)

div*_*210 10

让我向你展示一个光荣但可怕的黑客:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)
Run Code Online (Sandbox Code Playgroud)

您现在可以使用此LET表单:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])
Run Code Online (Sandbox Code Playgroud)

这使: [0, 3, 8]

  • 这太棒了!我想我下次写 Python 的时候会用到这个。我主要是一个 Lisp 和 JS 程序员,缺少多行 Lambada 很痛苦。这是获得它的一种方式。 (3认同)
  • 最初发布于https://gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb (2认同)

hei*_*wol 10

让我也对不同的解决方法提出我的两分钱。

简单的单行 lambda 与普通函数有何不同?我只能想到缺少赋值、一些类似循环的结构(for、while)、try- except 子句……就这样吗?我们甚至还有一个三元运算符 - 酷!因此,让我们尝试解决这些问题。

作业

这里有些人正确地指出,我们应该看看 Lisp 的let形式,它允许本地绑定。实际上,所有非状态改变的赋值只能用 来执行let。但每个 Lisp 程序员都知道letform 绝对等同于调用 lambda 函数!这意味着

(let ([x_ x] [y_ y])
  (do-sth-with-x-&-y x_ y_))
Run Code Online (Sandbox Code Playgroud)

是相同的

((lambda (x_ y_)
   (do-sth-with-x-&-y x_ y_)) x y)
Run Code Online (Sandbox Code Playgroud)

所以 lambda 已经足够了!每当我们想要进行新的分配时,我们只需添加另一个 lambda 并调用它即可。考虑这个例子:

def f(x):
    y = f1(x)
    z = f2(x, y)
    return y,z
Run Code Online (Sandbox Code Playgroud)

lambda 版本如下所示:

f = lambda x: (lambda y: (y, f2(x,y)))(f1(x))
Run Code Online (Sandbox Code Playgroud)

let如果您不喜欢在对数据执行操作后写入数据,您甚至可以创建该函数。你甚至可以柯里化它(只是为了更多的括号:))

let = curry(lambda args, f: f(*args))
f_lmb = lambda x: let((f1(x),), lambda y: (y, f2(x,y)))
# or:
f_lmb = lambda x: let((f1(x),))(lambda y: (y, f2(x,y)))

# even better alternative:
let = lambda *args: lambda f: f(*args)
f_lmb = lambda x: let(f1(x))(lambda y: (y, f2(x,y)))
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。但是如果我们必须重新分配,即改变状态怎么办?好吧,我认为只要所讨论的任务不涉及循环,我们就可以在不改变状态的情况下绝对幸福地生活。

循环

虽然 for 循环没有直接的 lambda 替代方案,但我相信我们可以编写非常通用的函数来满足我们的需求。看看这个斐波那契函数:

def fib(n):
    k = 0
    fib_k, fib_k_plus_1 = 0, 1
    while k < n:
        k += 1
        fib_k_plus_1, fib_k = fib_k_plus_1 + fib_k, fib_k_plus_1
    return fib_k
Run Code Online (Sandbox Code Playgroud)

显然,就 lambda 而言是不可能的。但是在编写了一些有用的函数之后,我们就完成了这个和类似的情况:

def loop(first_state, condition, state_changer):
    state = first_state
    while condition(*state):
        state = state_changer(*state)
    return state

fib_lmb = lambda n:\
            loop(
              (0,0,1),
              lambda k, fib_k, fib_k_plus_1:\
                k < n,
              lambda k, fib_k, fib_k_plus_1:\
                (k+1, fib_k_plus_1, fib_k_plus_1 + fib_k))[1]
Run Code Online (Sandbox Code Playgroud)

当然,如果可能的话,应该始终考虑使用map,reduce和其他高阶函数。

Try- except 和其他控制结构

解决此类问题的一般方法似乎是利用惰性求值,用不接受参数的 lambda 替换代码块:

def f(x):
    try:    return len(x)
    except: return 0
# the same as:
def try_except_f(try_clause, except_clause):
    try: return try_clause()
    except: return except_clause()
f = lambda x: try_except_f(lambda: len(x), lambda: 0)
# f(-1) -> 0
# f([1,2,3]) -> 3
Run Code Online (Sandbox Code Playgroud)

当然,这并不是 try-except 子句的完整替代方案,但您始终可以使其更加通用。顺便说一句,通过这种方法,您甚至可以使if行为变得像函数一样!

总结:很自然地,所提到的一切都让人感觉有点不自然并且不那么漂亮。尽管如此——它有效!并且无需任何evals其他技巧,因此所有智能感知都将起作用。我也不是说你应该在任何地方使用它。大多数情况下,您最好定义一个普通函数。我只是表明没有什么是不可能的。


You*_*eng 9

Python3.8之后,还有一种本地绑定的方法

lambda x: (
    y := x + 1,
    y ** 2
)[-1]
Run Code Online (Sandbox Code Playgroud)

For循环

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)],
    y
)[-1]
Run Code Online (Sandbox Code Playgroud)

如果分支

lambda x: (
    y := x ** 2,
    x > 5 and [y := y + x for _ in range(10)],
    y
)[-1]
Run Code Online (Sandbox Code Playgroud)

或者

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)] if x > 5 else None,
    y
)[-1]
Run Code Online (Sandbox Code Playgroud)

While 循环

import itertools as it
lambda x: (
    l := dict(y = x ** 2),
    cond := lambda: l['y'] < 100,
    body := lambda: l.update(y = l['y'] + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l['y']
)[-1]
Run Code Online (Sandbox Code Playgroud)

或者

import itertools as it
from types import SimpleNamespace as ns
lambda x: (
    l := ns(y = x ** 2),
    cond := lambda: l.y < 100,
    body := lambda: vars(l).update(y = l.y + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l.y
)[-1]
Run Code Online (Sandbox Code Playgroud)

或者

import itertools as it
lambda x: (
    y := x ** 2,
    *it.takewhile(lambda t: t[0],
    ((
    pred := y < 100,
    pred and (y := y + x))
    for _ in it.count())),
    y
)[-1]
Run Code Online (Sandbox Code Playgroud)


Wai*_*ung 6

让我试着解决@balpha解析问题.我会在多行lamda周围使用括号.如果没有括号,则lambda定义是贪婪的.所以lambda在

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))
Run Code Online (Sandbox Code Playgroud)

返回一个返回的函数 (y*z, [1,2,3])

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))
Run Code Online (Sandbox Code Playgroud)

手段

map(func, [1,2,3])
Run Code Online (Sandbox Code Playgroud)

其中func是返回y*z的多行lambda.那样有用吗?


小智 6

我在某些项目中实践这种肮脏的技巧感到内which,这有点简单:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]
Run Code Online (Sandbox Code Playgroud)

我希望您能找到一种保持pythonic的方法,但是如果您必须这样做的话,那么会比使用exec和操作globals减轻痛苦。


小智 5

(对于仍然对该主题感兴趣的任何人。)

考虑一下这一点(甚至包括在“多行”lambda 中的进一步语句中使用语句的返回值,尽管它丑到令人呕吐;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12
Run Code Online (Sandbox Code Playgroud)