【问题标题】:Assignment inside lambda expression in PythonPython中的lambda表达式内的赋值
【发布时间】:2011-09-11 01:00:22
【问题描述】:

我有一个对象列表,我想使用filterlambda 表达式删除除一个之外的所有空对象。

例如,如果输入是:

[Object(name=""), Object(name="fake_name"), Object(name="")]

...那么输出应该是:

[Object(name=""), Object(name="fake_name")]

有没有办法给lambda 表达式添加赋值?例如:

flag = True 
input = [Object(name=""), Object(name="fake_name"), Object(name="")] 
output = filter(
    (lambda o: [flag or bool(o.name), flag = flag and bool(o.name)][0]),
    input
)

【问题讨论】:

  • 没有。但你不需要这个。实际上,我认为即使可行,实现这一目标也是一种非常模糊的方式。
  • 为什么不直接将常规的旧函数传递给过滤器?
  • 我想使用 lambda,所以它会是一个非常紧凑的解决方案。我记得在 OCaml 中,我可以在返回表达式之前链接打印语句,认为这可以在 Python 中复制
  • 在开发链式管道的流程中非常痛苦,然后意识到:“哦,我想创建一个临时变量以使流程更清晰”或“我想记录这个中间步骤" : 然后你必须跳到其他地方创建一个函数来执行它:并且 name 该函数并跟踪它 - 即使它只在一个地方使用。
  • 另请参阅Assignment inside lambda expression in Python - Stack Overflow 了解特殊情况(C 中的++ 或 -- 运算符)

标签: python lambda expression variable-assignment


【解决方案1】:

The assignment expression operator := added in Python 3.8 支持在 lambda 表达式中赋值。由于语法原因,此运算符只能出现在带括号的 (...)、括号 [...] 或括号 {...} 表达式中。例如,我们将能够编写以下内容:

import sys
say_hello = lambda: (
    message := "Hello world",
    sys.stdout.write(message + "\n")
)[-1]
say_hello()

在 Python 2 中,可以将本地赋值作为列表推导的副作用。

import sys
say_hello = lambda: (
    [None for message in ["Hello world"]],
    sys.stdout.write(message + "\n")
)[-1]
say_hello()

但是,在您的示例中无法使用其中任何一个,因为您的变量 flag 位于外部范围内,而不是 lambda 的范围内。这与lambda 无关,它是Python 2 中的一般行为。Python 3 允许您使用defs 内部的nonlocal 关键字解决此问题,但不能在内部使用nonlocal lambdas.

有一个解决方法(见下文),但是当我们讨论这个主题时......


在某些情况下,您可以使用它来执行 lambda 内的所有操作:

(lambda: [
    ['def'
        for sys in [__import__('sys')]
        for math in [__import__('math')]

        for sub in [lambda *vals: None]
        for fun in [lambda *vals: vals[-1]]

        for echo in [lambda *vals: sub(
            sys.stdout.write(u" ".join(map(unicode, vals)) + u"\n"))]

        for Cylinder in [type('Cylinder', (object,), dict(
            __init__ = lambda self, radius, height: sub(
                setattr(self, 'radius', radius),
                setattr(self, 'height', height)),

            volume = property(lambda self: fun(
                ['def' for top_area in [math.pi * self.radius ** 2]],

                self.height * top_area))))]

        for main in [lambda: sub(
            ['loop' for factor in [1, 2, 3] if sub(
                ['def'
                    for my_radius, my_height in [[10 * factor, 20 * factor]]
                    for my_cylinder in [Cylinder(my_radius, my_height)]],

                echo(u"A cylinder with a radius of %.1fcm and a height "
                     u"of %.1fcm has a volume of %.1fcm³."
                     % (my_radius, my_height, my_cylinder.volume)))])]],

    main()])()

一个半径为 10.0cm、高为 20.0cm 的圆柱体的体积为 6283.2cm³。
一个半径为20.0cm、高40.0cm的圆柱体的体积为50265.5cm³。
一个半径为 30.0cm、高为 60.0cm 的圆柱体的体积为 169646.0cm³。

请不要。


...回到你原来的例子:虽然你不能在外部范围内对flag 变量执行赋值,但你可以使用函数来修改之前赋值的值。

例如,flag 可以是我们使用setattr 设置其.value 的对象:

flag = Object(value=True)
input = [Object(name=''), Object(name='fake_name'), Object(name='')] 
output = filter(lambda o: [
    flag.value or bool(o.name),
    setattr(flag, 'value', flag.value and bool(o.name))
][0], input)
[Object(name=''), Object(name='fake_name')]

如果我们想适应上述主题,我们可以使用列表推导来代替setattr

    [None for flag.value in [bool(o.name)]]

但实际上,在严肃的代码中,如果您要进行外部赋值,您应该始终使用常规函数定义而不是 lambda

flag = Object(value=True)
def not_empty_except_first(o):
    result = flag.value or bool(o.name)
    flag.value = flag.value and bool(o.name)
    return result
input = [Object(name=""), Object(name="fake_name"), Object(name="")] 
output = filter(not_empty_except_first, input)

【讨论】:

  • 此答案中的最后一个示例不会产生与示例相同的输出,但在我看来示例输出不正确。
  • 简而言之,这可以归结为:使用.setattr() 和类似方法(例如,字典 也应该这样做)将副作用破解到函数代码中,很酷的代码由@JeremyBanks 显示:)
  • 感谢assignment operator 上的注释!
【解决方案2】:

您不能真正维护 filter/lambda 表达式中的状态(除非滥用全局命名空间)。但是,您可以使用在 reduce() 表达式中传递的累积结果来实现类似的效果:

>>> f = lambda a, b: (a.append(b) or a) if (b not in a) else a
>>> input = ["foo", u"", "bar", "", "", "x"]
>>> reduce(f, input, [])
['foo', u'', 'bar', 'x']
>>> 

当然,您可以稍微调整一下条件。在这种情况下,它会过滤掉重复项,但您也可以使用 a.count(""),例如,仅限制空字符串。

不用说,你可以这样做,但你真的不应该这样做。 :)

最后,你可以用纯 Python 做任何事情lambda: http://vanderwijk.info/blog/pure-lambda-calculus-python/

【讨论】:

    【解决方案3】:

    没有必要使用 lambda,当您可以删除 所有 null 的时候,如果输入大小发生变化,则将其放回去:

    input = [Object(name=""), Object(name="fake_name"), Object(name="")] 
    output = [x for x in input if x.name]
    if(len(input) != len(output)):
        output.append(Object(name=""))
    

    【讨论】:

    • 我认为你的代码有一个小错误。第二行应该是output = [x for x in input if x.name]
    • 元素的顺序可能很重要。
    【解决方案4】:

    普通赋值 (=) 在 lambda 表达式中是不可能的,尽管可以与 setattr 和朋友一起执行各种技巧。

    然而,解决您的问题实际上非常简单:

    input = [Object(name=""), Object(name="fake_name"), Object(name="")]
    output = filter(
        lambda o, _seen=set():
            not (not o and o in _seen or _seen.add(o)),
        input
        )
    

    这会给你

    [Object(Object(name=''), name='fake_name')]
    

    如您所见,它保留了第一个空白实例,而不是最后一个。如果您需要最后一个,请反转进入filter 的列表,并反转来自filter 的列表:

    output = filter(
        lambda o, _seen=set():
            not (not o and o in _seen or _seen.add(o)),
        input[::-1]
        )[::-1]
    

    这会给你

    [Object(name='fake_name'), Object(name='')]
    

    需要注意的一件事:为了使其与任意对象一起使用,这些对象必须正确实现 __eq____hash__,如 here 所述。

    【讨论】:

      【解决方案5】:

      更新

      [o for d in [{}] for o in lst if o.name != "" or d.setdefault("", o) == o]
      

      或使用filterlambda

      flag = {}
      filter(lambda o: bool(o.name) or flag.setdefault("", o) == o, lst)
      

      上一个答案

      好的,你还在坚持使用 filter 和 lambda 吗?

      似乎这会更好地与字典理解一起使用,

      {o.name : o for o in input}.values()
      

      我认为 Python 不允许在 lambda 中赋值的原因类似于它不允许在理解中赋值的原因,这与这些东西在 C 方面进行评估的事实有关因此可以提高我们的速度。至少这是我阅读one of Guido's essays后的印象。

      我的猜测是,这也违背了在 Python 中使用一个正确的方式做任何一件事的哲学。

      【讨论】:

      • 所以这并不完全正确。它不会保留顺序,也不会保留非空字符串对象的副本。
      【解决方案6】:

      TL;DR:使用函数式惯用语时最好编写函数式代码

      正如许多人所指出的,在 Python 中不允许进行 lambda 赋值。一般来说,在使用功能性习语时,您最好以功能性方式思考,这意味着尽可能没有副作用和任务。

      这是使用 lambda 的函数式解决方案。为了清楚起见,我已将 lambda 分配给 fn(而且因为它有点长)。

      from operator import add
      from itertools import ifilter, ifilterfalse
      fn = lambda l, pred: add(list(ifilter(pred, iter(l))), [ifilterfalse(pred, iter(l)).next()])
      objs = [Object(name=""), Object(name="fake_name"), Object(name="")]
      fn(objs, lambda o: o.name != '')
      

      您也可以通过稍微改变一些东西来处理迭代器而不是列表。您还有一些不同的导入。

      from itertools import chain, islice, ifilter, ifilterfalse
      fn = lambda l, pred: chain(ifilter(pred, iter(l)), islice(ifilterfalse(pred, iter(l)), 1))
      

      您始终可以重新组织代码以减少语句的长度。

      【讨论】:

        【解决方案7】:

        如果我们可以代替flag = True 进行导入,那么我认为这符合标准:

        >>> from itertools import count
        >>> a = ['hello', '', 'world', '', '', '', 'bob']
        >>> filter(lambda L, j=count(): L or not next(j), a)
        ['hello', '', 'world', 'bob']
        

        或者过滤器最好写成:

        >>> filter(lambda L, blank_count=count(1): L or next(blank_count) == 1, a)
        

        或者,只是一个简单的布尔值,没有任何导入:

        filter(lambda L, use_blank=iter([True]): L or next(use_blank, False), a)
        

        【讨论】:

          【解决方案8】:

          在迭代期间跟踪状态的 Python 方法是使用生成器。 itertools 的方式很难理解恕我直言,试图破解 lambdas 来做到这一点是很愚蠢的。我会尝试:

          def keep_last_empty(input):
              last = None
              for item in iter(input):
                  if item.name: yield item
                  else: last = item
              if last is not None: yield last
          
          output = list(keep_last_empty(input))
          

          总体而言,可读性每次都胜过紧凑性。

          【讨论】:

            【解决方案9】:

            不,您不能在 lambda 中放置赋值,因为它有自己的定义。如果您使用函数式编程,那么您必须假设您的值是不可变的。

            一种解决方案是以下代码:

            output = lambda l, name: [] if l==[] \
                         else [ l[ 0 ] ] + output( l[1:], name ) if l[ 0 ].name == name \
                         else output( l[1:], name ) if l[ 0 ].name == "" \
                         else [ l[ 0 ] ] + output( l[1:], name )
            

            【讨论】:

              【解决方案10】:

              如果您需要一个 lambda 来记住调用之间的状态,我会推荐在本地命名空间中声明的函数或具有重载 __call__ 的类。既然我对您尝试做的事情的所有警告都已排除,我们可以得到您查询的实际答案。

              如果你真的需要让你的 lambda 在调用之间有一些内存,你可以这样定义它:

              f = lambda o, ns = {"flag":True}: [ns["flag"] or o.name, ns.__setitem__("flag", ns["flag"] and o.name)][0]
              

              那么您只需将f 传递给filter()。如果你真的需要,你可以通过以下方式取回flag 的值:

              f.__defaults__[0]["flag"]
              

              或者,您可以通过修改globals() 的结果来修改全局命名空间。不幸的是,你不能修改本地命名空间,就像修改locals() 的结果不会影响本地命名空间一样。

              【讨论】:

              • 或者直接使用原来的 Lisp:(let ((var 42)) (lambda () (setf var 43))).
              【解决方案11】:

              您可以使用绑定函数来使用伪多语句 lambda。然后,您可以使用封装类为 Flag 启用分配。

              bind = lambda x, f=(lambda y: y): f(x)
              
              class Flag(object):
                  def __init__(self, value):
                      self.value = value
              
                  def set(self, value):
                      self.value = value
                      return value
              
              input = [Object(name=""), Object(name="fake_name"), Object(name="")]
              flag = Flag(True)
              output = filter(
                          lambda o: (
                              bind(flag.value, lambda orig_flag_value:
                              bind(flag.set(flag.value and bool(o.name)), lambda _:
                              bind(orig_flag_value or bool(o.name))))),
                          input)
              

              【讨论】:

                【解决方案12】:

                有点混乱的解决方法,但是 lambdas 中的赋值无论如何都是非法的,所以这并不重要。您可以使用内置的 exec() 函数从 lambda 内部运行赋值,例如以下示例:

                >>> val
                Traceback (most recent call last):
                  File "<pyshell#31>", line 1, in <module>
                    val
                NameError: name 'val' is not defined
                >>> d = lambda: exec('val=True', globals())
                >>> d()
                >>> val
                True
                

                【讨论】:

                  【解决方案13】:

                  首先,您不需要为您的工作使用本地分配,只需检查上述答案

                  其次,使用 locals() 和 globals() 获取变量表然后更改值很简单

                  检查此示例代码:

                  print [locals().__setitem__('x', 'Hillo :]'), x][-1]
                  

                  如果您需要更改向环境添加全局变量,请尝试将 locals() 替换为 globals()

                  python 的 list comp 很酷,但大多数传统项目不接受这个(比如 flask :[)

                  希望对你有帮助

                  【讨论】:

                  • 您不能使用locals(),它在文档中明确指出更改它实际上不会更改本地范围(或者至少不会总是)。另一方面,globals() 按预期工作。
                  • @JPvdMerwe 试试看,不要盲从文档。并且 lambda 中的分配已经打破规则
                  • 不幸的是它只能在全局命名空间中工作,在这种情况下你真的应该使用globals()pastebin.com/5Bjz1mR4(在 2.6 和 3.2 中测试)证明了这一点。
                  猜你喜欢
                  • 1970-01-01
                  • 2021-09-01
                  • 2017-04-21
                  • 1970-01-01
                  • 2018-10-09
                  • 1970-01-01
                  • 2016-07-30
                  • 2021-11-23
                  相关资源
                  最近更新 更多