【问题标题】:No Multiline Lambda in Python: Why not?Python 中没有多行 Lambda:为什么不呢?
【发布时间】:2010-11-17 00:49:53
【问题描述】:

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

现在,我确信 Guido 有理由不在语言中包含多行 lambda,但出于好奇:在什么情况下包含多行 lambda 会产生歧义?我听说的是真的,还是 Python 不允许多行 lambda 的其他原因?

【问题讨论】:

  • tl;dr 版本: 因为 Python 是一种没有 { } 块的惰性语言,所以为了保持一致的句法设计,这是不允许的。
  • 另外:我很惊讶没有人在答案中提到这一点...您可以在 Python 中以 \ 字符结束行并继续下一行...此信息有点取代了整个问题,所以......
  • “句法设计”
  • @chepner 除了def 不能与它打算使用的逻辑内联:你必须把它放在其他地方,然后读者必须去寻找它。对只使用一次的代码使用def 是 Python 语言的一个严重缺陷:这些只应该用于代码重用。

标签: python syntax lambda


【解决方案1】:

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

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

【讨论】:

  • 为什么这不是最佳答案?正如发明者明确指出的那样,这与技术原因无关,而是一种设计选择。
  • @DanAbramov 因为 OP 可能好几年没有登录了。
  • Guido 的回答只是我希望 Python 不依赖缩进来定义块的另一个原因。
  • 我不确定我是否会将“直觉”称为设计选择。 ;)
  • 我还没有跳上 PyTrain,但我一直在考虑它。仅此一件事,我必须用实际名称定义一个实际函数,以便将其用作更高阶,这让我想吐,让我不想写 Python。 “非pythonic”?杀了我。
【解决方案2】:

看下面:

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

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

在括号内,缩进对 python 来说并不重要,所以你不能明确地使用多行。

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

【讨论】:

  • 如果你想从 lambda 返回一个元组,他们可以强制使用括号。 IMO,这应该一直被强制执行以防止这种歧义,但是哦,好吧。
  • 这是一个简单的歧义,必须通过添加一组额外的括号来解决,这在许多地方已经存在,例如由其他参数包围的生成器表达式,在整数文字上调用方法(尽管不必如此,因为函数名不能以数字开头),当然还有单行 lambda(可能是长表达式写在多行上)。多行 lambda 与这些情况并没有什么特别的不同,它保证在此基础上排除它们。 This 才是真正的答案。
  • 我喜欢有无数种语言可以毫不费力地处理它,但不知何故,有一些深刻的原因表明它即使不是不可能也非常困难
  • @nicolas 简而言之就是 python
  • 对于函数式编程,我会选择 Scala 而不是 Python。
【解决方案3】:

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

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

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

例子:

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

【讨论】:

  • 我的老板只是在我们的 PyQt 应用程序中要求这样的东西。太棒了!
  • 谢谢你,我也在寻找一种使用短(但仍然是多行)lambdas 作为 PySide UI 回调的好方法。
  • 现在我看到它立即建议使用lambda argsetattr(arg, 'attr','value') 来颠覆“无作业......”。然后是对andor 的短路评估……这是 Javascript 做的。扎根于你,就像常春藤扎入一堵墙。我几乎希望我能在圣诞节忘记这件事。
  • 非常聪明 - 并且非常易读。现在 - 关于那些(缺少..)assignments ..
  • @nigel222 为什么会感到羞耻? Python 语言从根本上被削弱了 - 但它是无论如何 用于大部分数据科学 的语言。所以我们进行调整。寻找产生副作用的方法(通常足够简单地打印/记录!)和分配(通常足以只是中间变量!)应该由语言很好地处理。但它们甚至不受支持(至少如果您遵循 PEP 准则)
【解决方案4】:

几个相关链接:

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

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

另外,我遇​​到了一个关于 Python 中 Ruby 样式块的有趣提议,Guido 在其中发布了一个响应,但实际上没有击落它(但不确定是否有任何后续击落):

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

【讨论】:

    【解决方案5】:

    [编辑编辑] 因为这个问题在被问到 12 年后仍然存在。我将继续每 4 年左右修改一次答案的传统。

    首先,问题是多行 lambda 如何与 Python 发生冲突。接受的答案显示了一个简单的例子。几年前我在下面链接的高度评价的答案回答了“为什么它不是 Python 的一部分”的问题——对于那些认为现有的“冲突”示例不足以使多-line lambda 无法在 Python 中实现。

    在此答案的先前迭代中,我讨论了如何按原样在 Python 中实现多行 lambda。我已经删除了那部分,因为这是一连串的坏习惯。如果您愿意,您可以在此答案的编辑历史记录中看到它。

    但是,“为什么不呢?”的答案,“因为罗森这么说”仍然会让人感到沮丧。所以让我们看看它是否可以围绕用户 balpha 给出的反例进行设计:

    map(lambda x:
            y=x+1 # <-- this line defines the outmost indent level*
            for i in range(12):
                y+=12
            return y
       , [1,2,3])
    
    #*By convention it is always one-indent past the 'l' in lambda
    

    至于返回值,我们有以下在python中是不允许的:

    def f():
      return 3
    , [1,2,3]
    

    所以按照同样的逻辑,“[1,2,3]”不应该是返回值的一部分。让我们换个方式试试吧:

    map(lambda x:
            y=x+1                    # part of lambda block
            for i in range(12):      # part of lambda block
                y+=12                # part of lambda block
            return y, [1,2,3])       # part of lambda block
    

    这个比较棘手,但是由于 lambda 块有一个明确定义的开头(标记“lambda”)但没有明确的结尾,我认为任何与 lambda 块的一部分在同一行的东西也是lambda 块。

    人们可能会想象一些功能可以识别右括号,甚至可以根据封闭元素预期的标记数量进行推断。总的来说,上述表达式似乎并非完全无法解析,但可能有点挑战。

    为了简单起见,您可以将所有不属于该块的字符分开:

    map(lambda x:
            y=x+1                    # part of lambda block
            for i in range(12):      # part of lambda block
                y+=12                # part of lambda block
            return y                 # part of lambda block
    , [1,2,3]) # argument separator, second argument, and closing paren for map
    

    回到我们原来的位置,但这一次是明确的,因为最后一行在 lambda 块的最低缩进深度后面。 单行 lambda 将是一种特殊情况(通过颜色后没有立即换行来识别),其行为与现在相同。

    这并不是说它必须是 Python 的一部分——但它是一个简单的说明,也许可以通过对语言。

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

    简单地说,它是不符合 Python 的。来自 Guido van Rossum 的博文:

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

    【讨论】:

      【解决方案6】:

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

      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]

      【讨论】:

      【解决方案7】:

      我对在我的一些更简单的项目中练习这种肮脏的技巧感到内疚:

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

      我希望你能找到一种保持 Python 风格的方法,但如果你必须这样做,这比使用 exec 和操作全局变量更痛苦。

      【讨论】:

      • 是的,差不多了。与赋值表达式一起使用会更有用。
      【解决方案8】:

      让我尝试解决@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。这行得通吗?

      【讨论】:

      • 我认为顶部应该返回map(func, [1,2,3]),底部应该是一个错误,因为 map 函数没有足够的参数。代码中还有一些额外的括号。
      • 将其放入运行 python2.7.13 的 pycharm 中会出现语法错误。
      • 额外的括号
      • 好吧,我们现在有表达式分配,它可以工作,请在下面查看我的答案
      【解决方案9】:

      (适用于仍然对该主题感兴趣的人。)

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

      【讨论】:

      • 这在使用不同参数第二次调用时不起作用并导致内存泄漏,除非第一行是state.clear(),因为默认参数仅在创建函数时创建一次。
      【解决方案10】:

      让我也为不同的解决方法投入两分钱。

      简单的单行 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)
      

      所以 lambdas 绰绰有余!每当我们想要进行新的分配时,我们只需添加另一个 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
      

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

      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 和其他技巧,所以所有的智能感知都可以工作。我也不是说你应该在任何地方使用它。大多数情况下,您最好定义一个普通函数。我只是表明没有什么是不可能的。

      【讨论】:

      • 太疯狂了!酷!
      • @aoeu256 我不太明白,请您提供一些示例/文档吗?
      【解决方案11】:

      这是一个更有趣的多行 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 肯定不会很有效率,但如果你受到限制,它可能是快速完成某事的好方法,然后稍后将其重写为实际函数。

      【讨论】:

        【解决方案12】:

        我知道这是一个老问题,但这里记录的是一种解决多行lambda问题的方法,其中一个调用的结果被另一个调用消耗。

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

        下面是一个简单的例子,我们从x = 3开始,然后在第一行添加1,然后在第二行添加2,得到6作为输出。

        from functools import reduce
        
        reduce(lambda data, func: func(data), [
            lambda x: x + 1,
            lambda x: x + 2
        ], 3)
        
        ## Output: 6
        

        【讨论】:

        • 这不是hacky,事实上几年前我只是把上面的函数做成了一个方便的函数:参数是apply(list, [fns])。但这仍然很尴尬。问题的根源在于 lambdas 只是吸收了 python。仅单个表达式,仅一行,甚至关键字 lambda 都变得非常老了非常快
        • 其实这是一种流行的方法。包toolz 有这个和其他花哨的功能。有了它,您的示例可能会改写为:toolz.pipe(3, lambda x: x + 1, lambda x: x + 3).
        • 你可以在 lambda 中使用赋值表达式,不需要链接 lambdas,在下面查看我的答案
        【解决方案13】:

        关于丑陋的 hack,您始终可以结合使用 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')
        

        【讨论】:

          【解决方案14】:

          如果您的 lambda 函数有多行,您可以简单地使用斜杠 (\)

          例子:

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

          【讨论】:

          • 这个问题与使用超过 1 个表达式而不是超过 1 个文字行有关。
          【解决方案15】:

          我只是在尝试使用 reduce 进行 dict 理解,然后想出一个 liner hack:

          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 字典理解中所做的一样:https://stackoverflow.com/a/11068265

          【讨论】:

            【解决方案16】:

            我从 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 表达式本身中做多行,也许它不能准确回答这个问题,但如果有人得到这个线程来寻找如何调试表达式(比如我),我认为会有帮助的

            【讨论】:

            • 我为什么要这样做而不是简单地写map(multiply, [1, 2, 3])
            • 那你必须去寻找multiply()所在的地方。它使人失去了代码流,不像javascript,其中映射逻辑包含在下面的块中
            【解决方案17】:

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

            例如,代码

            #%%
            x = 1
            y = 2
            
            q = list(map(lambda t: (tx := t*x, ty := t*y, tx+ty)[-1], [1, 2, 3]))
            
            print(q)
            

            将打印 [3, 6, 9]

            【讨论】:

              【解决方案18】:

              一种在 lambda 项之间传递任意数量的变量的安全方法:

              print((lambda: [
                  locals().__setitem__("a", 1),
                  locals().__setitem__("b", 2),
                  locals().__setitem__("c", 3),
                  locals().get("a") + locals().get("b") + locals().get("c")
              ])()[-1])
              

              输出:6

              【讨论】:

                【解决方案19】:

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

                【讨论】:

                • 不,任何编写事件驱动程序的人都会告诉你多行 lambda 很重要。
                • python是事件驱动程序吗?
                • 那么,你认为仅仅因为它是用 Python 编写的,就意味着 GUI 必须完全命令式地完成?
                • 您应该以 Python 方式处理它,而不是堆叠 lambda。
                猜你喜欢
                • 2014-08-10
                • 1970-01-01
                • 2016-02-10
                • 1970-01-01
                • 2011-09-30
                • 1970-01-01
                相关资源
                最近更新 更多