Python中lambda表达式内的赋值
我有一个对象列表,我想要去掉所有空的对象,只保留一个,使用的是filter
和lambda
表达式。
比如说,如果输入是:
[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 个回答
在 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__
,具体可以参考 这里。
在一个 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/
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)