Python中Lambda表达式内的赋值


105

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

1
不,但是您不需要这个。实际上,我认为即使达到目的,这也是实现这一目标的一种非常模糊的方法。

8
为什么不将常规的旧函数传递给过滤器呢?
dfb

5
我想使用lambda只是为了它是一个非常紧凑的解决方案。我记得在OCaml中,我可以链print语句返回表达式之前,认为这可能会在Python被复制

在开发链式pipeilne的流程中然后意识到:“哦,我想创建一个临时变量以使流程更清晰”或“我想记录此中间步骤”,这是非常痛苦的:然后您必须跳过在其他地方创建可以执行此功能的函数:并命名该函数并跟踪它-即使仅在一个地方使用它。
javadba

Answers:


215

:=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中的常规行为。Python3可让您nonlocaldefs 内使用关键字解决此问题,但nonlocal不能在lambdas 内使用。

有一种解决方法(请参阅下文),但是当我们讨论该主题时...


在某些情况下,您可以使用它来执行a内部的所有操作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可以是.value我们使用设置的对象setattr

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如果要进行外部分配,则应始终使用常规函数定义而不是a 。

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的很酷的代码:)
jno 2013年

谢谢assignment operator
javadba

37

您不能真正维护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中执行任何操作lambdahttp : //vanderwijk.info/blog/pure-lambda-calculus-python/


17

当您可以删除所有空值并在输入大小更改时放回一个值时,就无需使用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=""))

1
我认为您的代码有一个小错误。第二行应为output = [x for x in input if x.name]
halex

元素的顺序可能很重要。
MAnyKey'3

15

尽管可以与和朋友一起执行各种技巧,但表达式=内部不可能进行普通赋值()。 lambdasetattr

但是,解决您的问题实际上非常简单:

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__作为解释在这里


7

更新

[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一边评估的,因此可以给我们一个增加速度。至少那是看完Guido的一篇文章后给我的印象。

我的猜测是,这也将违背其哲学一个在Python做任何一件事情的正确方法。


因此,这并不完全正确。它不会保留顺序,也不会保留非空字符串对象的重复项。
JPvdMerwe

7

TL; DR:使用功能惯用法时,最好编写功能代码

正如许多人指出的那样,在Python中不允许进行lambda赋值。通常,在使用功能性习惯用法时,您最好以功能性方式进行思考,这意味着在没有副作用的情况下也没有赋值。

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

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

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

6

在迭代过程中跟踪状态的Python方法是使用生成器。itertools的方法很难理解恕我直言,而试图破解lambda来做到这一点显然是愚蠢的。我会尝试:

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

总体而言,可读性每次都比紧凑性好。


4

不可以,由于其自​​身的定义,您无法将分配放入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 )

4

如果您需要一个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)))
卡兹(Kaz)

4

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

0

有点麻烦的解决方法,但是在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

-2

首先,您不需要为工作使用本地服务,只需检查以上答案

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

检查以下示例代码:

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

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

python的list comp很酷,但是大多数triditional项目不接受此命令(例如flask:[)

希望它可以帮助


2
您不能使用locals(),它在文档中明确指出更改它实际上并不会更改本地范围(或者至少不会总是如此)。globals()另一方面,按预期方式工作。
JPvdMerwe,2013年

@JPvdMerwe只需尝试一下,不要盲目遵循文档。并且lambda中的赋值已经违反了规则
jyf1987

3
不幸的是,它仅适用于全局名称空间,在这种情况下,您确实应该使用globals()pastebin.com/5Bjz1mR4(在2.6和3.2中都进行了测试)证明了这一点。
JPvdMerwe,2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.