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

时间:2009-08-05 14:00:30

标签: python syntax lambda

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

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

18 个答案:

答案 0 :(得分:544)

Guido van Rossum(Python的发明者)在an old blog post中自己回答了这个问题 基本上,他承认这在理论上是可行的,但任何提议的解决方案都是非Pythonic:

  

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

答案 1 :(得分:131)

请看以下内容:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

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

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

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

答案 2 :(得分:44)

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

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

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

示例:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())

答案 3 :(得分:17)

一些相关链接:

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

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

此外,这里有一个有趣的Python样式块建议我跑过去Guido发布了一个没有实际拍摄的响应(不确定是否有任何后续的拍摄):

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

答案 4 :(得分:11)

[编辑]阅读this answer.它解释了为什么多行lambda不是一个东西。

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

  

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

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

1 你会对使用一行python做些什么感到惊讶。


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

(lambda n: (exec('global x; x=4; x=x+n'), x-2)[-1])(0)

它的作用:

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

  • 例如,lambda x: (functionA(), functionB(), functionC(), 0)[-1] 即使是唯一的信息,也会执行所有三个功能 使用的是列表中的最后一项(0)。

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

  • 请注意,除非您将变量声明为global,否则它不会在exec函数调用之外存在(这仅适用于exec个函数{ {1}}陈述)。

  • 例如,lambda在没有(lambda: exec('x=5;print(x)'))()声明的情况下正常工作。但是,global(lambda: (exec('x=5'), exec('print(x)')))()不会。

  • 请注意,所有(lambda: (exec('x=5'), x)()变量都存储在全局命名空间中,并在函数调用完成后继续存在。由于这个原因,这不是一个好的解决方案,如果可能的话应该避免。 global从lambda函数中的global函数声明的变量与{{1}保持独立命名空间。 (在Python 3.3.3中测试)

  • 元组末尾的exec获取最后一个索引。例如,global[-1]。这样做只会返回所需的输出值,而不是包含来自[1,2,3,4][-1]函数和其他无关值的4的整个元组。

等效多线功能:

None

避免需要多行lambda的方法:

递归:

exec

布尔是整数:

def function(n):
    x = 4
    x = x+n
    return x-2

function(0)

迭代器:

f = lambda i: 1 if i==0 or i==1 else f(i-1)+f(i-2)

答案 5 :(得分: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)

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

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

给出:[0, 3, 8]

答案 6 :(得分:6)

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

中的lambda
map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

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

的函数

但是

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

装置

map(func, [1,2,3])

其中func是返回y * z的多行lambda。这有用吗?

答案 7 :(得分:4)

(对于仍然对这个主题感兴趣的人。)

考虑这一点(甚至包括在“多行”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

答案 8 :(得分:3)

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

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

我希望您能找到一种保持pythonic的方法,但是如果您必须这样做的话,这比使用exec和操纵全局变量要容易得多。

答案 9 :(得分:2)

这是多行lambda的更有趣的实现。由于python如何使用缩进来构造代码,因此无法实现。

但幸运的是,可以使用数组和括号禁用缩进格式。

正如已经指出的那样,您可以这样编写代码:

lambda args: (expr1, expr2,... exprN)

从理论上讲,如果可以保证从左到右进行求值,那么它会起作用,但是您仍然会丢失从一个表达式传递到另一个表达式的值。

一种更冗长的方法是拥有

lambda args: [lambda1, lambda2, ..., lambdaN]

每个lambda接收前一个参数的地方。

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

此方法可让您编写有点像Lisp / scheme的内容。

所以您可以这样写:

let(lambda x, y: x+y)((1, 2))

可以使用更复杂的方法来计算斜边

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

这将返回一个标量数字列表,以便可以将多个值减少为一个。

拥有很多lambda肯定不会非常有效,但是如果您受到限制,它可能是一种快速完成某项工作并将其重写为实际功能的好方法。

答案 10 :(得分:2)

让我也就不同的解决方法投入两美分。

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

任务

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

(let ([x_ x] [y_ y])
  (do-sth-with-x-&-y x_ y_))

相同
((lambda (x_ y_)
   (do-sth-with-x-&-y x_ y_)) x y)

因此,lambda绰绰有余!每当我们要进行新的分配时,我们只需添加另一个lambda并调用它。考虑以下示例:

def f(x):
    y = f1(x)
    z = f2(x, y)
    return y,z

lambda版本如下:

f = lambda x: (lambda y: (y, f2(x,y)))(f1(x))

如果您不喜欢在对数据执行操作后写入数据,甚至可以使用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)))

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

循环

虽然没有直接的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

显然,就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]

当然,应该尽可能考虑使用mapreduce和其他高阶函数。

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

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

总结:提到的所有内容都感觉有点不自然,而且不是Python般的美丽,这是很自然的。尽管如此,它仍然有效!并且没有任何evals和其他度量标准,因此所有智能感知都将起作用。我也不是说您应该在任何地方都使用它。通常,您最好定义一个普通函数。我只表明没有什么是不可能的。

答案 11 :(得分:1)

关于丑陋的黑客,您总是可以结合使用exec和常规函数来定义多行函数,如下所示:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

您可以将其包装为以下功能:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')

答案 12 :(得分:1)

我从python开始,但是最明显的方式来自Javascript,是将表达式提取为函数。...

人为的例子,乘法表达式(x*2)被提取为函数,因此我可以使用多行:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

也许这不能完全回答问题,这是否是如何在lambda表达式本身中做多行,但是如果有人让这个线程在寻找如何调试表达式的方法(像我一样),我认为它会帮助

答案 13 :(得分:1)

我知道这是一个老问题,但是根据记录,这里是对多行lambda问题的一种解决方案,在该问题中,一个呼叫的结果被另一个呼叫消耗。

我希望它不是超级hacky,因为它仅基于标准库函数并且不使用dunder方法。

下面是一个简单的示例,其中我们以x = 3开始,然后在第一行中添加1,然后在第二行中添加2并得到{{1} }作为输出。

6

答案 14 :(得分:0)

我只是在玩些游戏,以尝试对reduce进行字典理解,并提出这个简单的技巧:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

我只是想做与本Javascript dict理解相同的事情:https://stackoverflow.com/a/11068265

答案 15 :(得分:0)

如果您的lambda函数有多行,则只需使用斜杠(\

示例:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30

答案 16 :(得分:0)

在 Python 3.8/3.9 中有赋值表达式,所以它可以在 lambda 中使用,大大 扩展功能

例如代码

multi_string_detect<-function(x,y){
        temp<-sapply(y, function(z){str_detect(x, z)})
        apply(temp, 1, any)
}

multi_string_detect(lorem, c('sit', 'non')
[1]  TRUE FALSE FALSE  TRUE

将打印 [3, 6, 9]

答案 17 :(得分:-2)

因为lambda函数应该是单行的,因为它是函数的最简单形式an entrance, then return