Python 中 lambda 表达式中的赋值

我有一个对象列表,我想删除除一个以外的所有空对象,使用 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
)
153498 次浏览

没有必要使用 lambda,当您可以删除空值的 所有,并在输入大小改变时放回一个:

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=""))

不,你不能把赋值放在 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 )

Python 3.8 中添加的赋值表达式操作符 :=支持 lambda 表达式内部的赋值。由于句法原因,此操作符只能出现在括号内的 (...)、括号内的 [...]或括号内的 {...}表达式中。例如,我们将能够写下列内容:

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

在 Python2中,作为列表理解的副作用,可以执行本地赋值。

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)

更新 :

[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)

上一页答案

好吧,你是不是一直在用滤波器和 lambda?

看来最好还是用字典理解,

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

我认为 Python 不允许在 lambda 中赋值的原因类似于它不允许在理解中赋值的原因这与这些东西在 C上被求值这样可以提高我们的速度有关。至少这是我读完 圭多的一篇散文后的印象。

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

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

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

检查以下示例代码:

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

如果需要更改向环境中添加全局变量,请尝试用 Globals ()替换 当地人

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

希望能有所帮助

您不能在 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/执行任何操作

如果需要 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()的结果不影响本地名称空间那样修改本地名称空间。

您可以使用 bind 函数来使用伪多语句 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)

正常分配(=)在 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__,正如 给你所解释的那样。

DR: 在使用函数习惯用法时,最好编写函数代码

正如许多人指出的那样,在 Python 中,lambdas 的赋值是不允许的。一般来说,当你使用功能性习语时,你最好用功能性的方式思考,这意味着只要有可能就没有副作用和任务。

下面是使用 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))

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

如果我们可以用导入代替 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)

在迭代过程中跟踪状态的 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))

总的来说,可读性每次都胜过紧凑性。

有点乱七八糟的解决办法,但是在 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