繁体   English   中英

Python 中 lambda 表达式中的赋值

[英]Assignment inside lambda expression in Python

我有一个对象列表,我想使用filterlambda表达式删除除一个之外的所有空对象。

例如,如果输入是:

[Object(name=""), Object(name="fake_name"), Object(name="")]

...那么 output 应该是:

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

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位于外部 scope 中,而不是lambda的 scope 中。 This doesn't have to do with lambda , it's the general behaviour in Python 2. Python 3 lets you get around this with the nonlocal keyword inside of def s, but nonlocal can't be used inside lambda s.

有一种解决方法(见下文),但是当我们讨论这个话题时......


在某些情况下,您可以使用它来执行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³。

请不要。


...回到你原来的例子:虽然你不能对外部 scope 中的flag变量执行赋值,但你可以使用函数来修改先前分配的值。

例如, flag可以是 object,我们使用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)]]

但实际上,在严肃的代码中,如果您要进行外部分配,您应该始终使用常规 function 定义而不是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)

您不能在filter / lambda表达式中真正维护 state(除非滥用全局命名空间)。 但是,您可以使用在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,当您可以删除所有null 时,如果输入大小发生变化,则放回一个:

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表达式中不可能进行正常赋值 ( = ),尽管可以使用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='')]

需要注意的一件事:为了让 this 与任意对象一起工作,这些对象必须正确实现__eq____hash__ ,如此所述。

更新

[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侧评估的事实有关速度的增加。 至少这是我读完圭多的一篇文章后的印象。

我的猜测是,这也将 go 反对在 Python 中以一种正确的方式做任何一件事的哲学。

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

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

如果不是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)

或者,只是一个简单的 boolean,没有任何导入:

filter(lambda L, use_blank=iter([True]): L or next(use_blank, False), a)

在迭代期间跟踪 state 的 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))

总体而言,可读性每次都胜过紧凑性。

If you need a lambda to remember state between calls, I would recommend either a function declared in the local namespace or a class with an overloaded __call__ . 既然我对您尝试做的事情的所有警告都已排除,我们可以得到您查询的实际答案。

如果你真的需要让你的 lambda 在调用之间有一些 memory ,你可以定义它:

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()的结果不影响本地命名空间相同的方式修改本地命名空间。

您可以使用绑定 function 来使用伪多语句 lambda。 然后,您可以使用包装器 class 作为标志以启用分配。

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 中进行分配,因为它有自己的定义。 如果您使用函数式编程,那么您必须假设您的值是不可变的。

一种解决方案是以下代码:

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 )

一种混乱的解决方法,但无论如何,lambdas 中的赋值都是非法的,所以这并不重要。 您可以使用内置的exec() function 从 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

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

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

检查此示例代码:

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

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

python 的列表组合很酷,但大多数传统项目不接受这个(如 flask:[)

希望它可以帮助

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM