在Python中使用lambda表达式进行赋值

时间:2011-06-08 16:23:36

标签: python lambda expression variable-assignment

我有一个对象列表,我想使用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 个答案:

答案 0 :(得分:189)

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允许您使用nonlocal s内的def关键字解决此问题,但{{1 }}不能在nonlocal s。

中使用

有一种解决方法(见下文),但我们正在谈论这个话题......


在某些情况下,您可以使用它来执行lambda

内的所有操作
lambda
  

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

请不要。


...回到原始示例:虽然您无法对外部作用域中的(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()])() 变量执行赋值,但您可以使用函数来修改先前指定的值。

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

.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

答案 1 :(得分:31)

您无法在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/

答案 2 :(得分:16)

当你可以移除所有空的时,不需要使用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=""))

答案 3 :(得分:11)

=表达式中无法进行正常分配(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='')]

需要注意的一点是:为了使其能够处理任意对象,这些对象必须按照here的说明正确实现__eq____hash__

答案 4 :(得分:6)

<强>更新

[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中进行赋值的原因类似于它不允许在理解中进行赋值的原因,而这与在{{1}上评估这些事物的事实有关。因此可以提高速度。至少这是我在阅读one of Guido's essays后的印象。

我的猜测是,这也违背了一个在Python中做任何事情的正确方法的理念。

答案 5 :(得分:5)

TL; 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))

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

答案 6 :(得分:5)

如果代替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)

答案 7 :(得分:5)

在迭代期间跟踪状态的pythonic方法是使用生成器。 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))

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

答案 8 :(得分:3)

不,由于其自​​己的定义,您无法在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 )

答案 9 :(得分:3)

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

答案 10 :(得分:3)

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

答案 11 :(得分:1)

有种凌乱的解决方法,但是在lambda中进行赋值仍然是非法的,因此这并不重要。您可以使用内置的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

答案 12 :(得分:-2)

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

第二,它简单地使用locals()和globals()来获取变量表然后更改值

检查此示例代码:

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

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

python的列表组件很酷但是大多数的triditional项目都不接受这个(比如烧瓶:[)

希望它可以提供帮助