Python中lambda表达式内的赋值

150 投票
13 回答
171794 浏览
提问于 2025-04-16 19:11

我有一个对象列表,我想要去掉所有空的对象,只保留一个,使用的是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
)

13 个回答

21

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__,具体可以参考 这里

41

在一个 filterlambda 表达式中,你其实不能保持状态(除非你去用全局命名空间,这样做不太好)。不过,你可以通过在 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/

277

在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允许你在 def 中使用 nonlocal 关键字来解决这个问题,但 nonlocal 不能在 lambda 中使用。

有一个变通方法(见下文),但既然我们提到这个话题...


在某些情况下,你可以在 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.0厘米,高度为20.0厘米的圆柱体的体积是6283.2立方厘米。
一个半径为20.0厘米,高度为40.0厘米的圆柱体的体积是50265.5立方厘米。
一个半径为30.0厘米,高度为60.0厘米的圆柱体的体积是169646.0立方厘米。

请不要这样做。


...回到你最初的例子:虽然你不能在外部作用域对 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)

撰写回答