问题: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
)

I have a list of objects and I want to remove all objects that are empty except for one, using filter and a lambda expression.

For example if the input is:

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

…then the output should be:

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

Is there a way to add an assignment to a lambda expression? For example:

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
)

回答 0

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

supports assignment inside of lambda expressions. This operator can only appear within a parenthesized (...), bracketed [...], or braced {...} expression for syntactic reasons. For example, we will be able to write the following:

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

In Python 2, it was possible to perform local assignments as a side effect of list comprehensions.

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

However, it’s not possible to use either of these in your example because your variable flag is in an outer scope, not the lambda‘s 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 defs, but nonlocal can’t be used inside lambdas.

There’s a workaround (see below), but while we’re on the topic…


In some cases you can use this to do everything inside of 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()])()

A cylinder with a radius of 10.0cm and a height of 20.0cm has a volume of 6283.2cm³.
A cylinder with a radius of 20.0cm and a height of 40.0cm has a volume of 50265.5cm³.
A cylinder with a radius of 30.0cm and a height of 60.0cm has a volume of 169646.0cm³.

Please don’t.


…back to your original example: though you can’t perform assignments to the flag variable in the outer scope, you can use functions to modify the previously-assigned value.

For example, flag could be an object whose .value we set using 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')]

If we wanted to fit the above theme, we could use a list comprehension instead of setattr:

    [None for flag.value in [bool(o.name)]]

But really, in serious code you should always use a regular function definition instead of a lambda if you’re going to be doing outer assignment.

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)

回答 1

您不能真正维护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/

You cannot really maintain state in a filter/lambda expression (unless abusing the global namespace). You can however achieve something similar using the accumulated result being passed around in a reduce() expression:

>>> 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']
>>> 

You can, of course, tweak the condition a bit. In this case it filters out duplicates, but you can also use a.count(""), for example, to only restrict empty strings.

Needless to say, you can do this but you really shouldn’t. :)

Lastly, you can do anything in pure Python lambda: http://vanderwijk.info/blog/pure-lambda-calculus-python/


回答 2

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

There’s no need to use a lambda, when you can remove all the null ones, and put one back if the input size changes:

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

尽管可以与和朋友一起执行各种技巧,但表达式=内部不可能进行普通赋值()。 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__作为解释在这里

Normal assignment (=) is not possible inside a lambda expression, although it is possible to perform various tricks with setattr and friends.

Solving your problem, however, is actually quite simple:

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
    )

which will give you

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

As you can see, it’s keeping the first blank instance instead of the last. If you need the last instead, reverse the list going in to filter, and reverse the list coming out of filter:

output = filter(
    lambda o, _seen=set():
        not (not o and o in _seen or _seen.add(o)),
    input[::-1]
    )[::-1]

which will give you

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

One thing to be aware of: in order for this to work with arbitrary objects, those objects must properly implement __eq__ and __hash__ as explained here.


回答 4

更新

[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做任何一件事情的正确方法。

UPDATE:

[o for d in [{}] for o in lst if o.name != "" or d.setdefault("", o) == o]

or using filter and lambda:

flag = {}
filter(lambda o: bool(o.name) or flag.setdefault("", o) == o, lst)

Previous Answer

OK, are you stuck on using filter and lambda?

It seems like this would be better served with a dictionary comprehension,

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

I think the reason that Python doesn’t allow assignment in a lambda is similar to why it doesn’t allow assignment in a comprehension and that’s got something to do with the fact that these things are evaluated on the C side and thus can give us an increase in speed. At least that’s my impression after reading one of Guido’s essays.

My guess is this would also go against the philosophy of having one right way of doing any one thing in Python.


回答 5

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

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

TL;DR: When using functional idioms it’s better to write functional code

As many people have pointed out, in Python lambdas assignment is not allowed. In general when using functional idioms your better off thinking in a functional manner which means wherever possible no side effects and no assignments.

Here is functional solution which uses a lambda. I’ve assigned the lambda to fn for clarity (and because it got a little long-ish).

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 != '')

You can also make this deal with iterators rather than lists by changing things around a little. You also have some different imports.

from itertools import chain, islice, ifilter, ifilterfalse
fn = lambda l, pred: chain(ifilter(pred, iter(l)), islice(ifilterfalse(pred, iter(l)), 1))

You can always reoganize the code to reduce the length of the statements.


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

If instead of flag = True we can do an import instead, then I think this meets the criteria:

>>> from itertools import count
>>> a = ['hello', '', 'world', '', '', '', 'bob']
>>> filter(lambda L, j=count(): L or not next(j), a)
['hello', '', 'world', 'bob']

Or maybe the filter is better written as:

>>> filter(lambda L, blank_count=count(1): L or next(blank_count) == 1, a)

Or, just for a simple boolean, without any imports:

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

回答 7

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

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

The pythonic way to track state during iteration is with generators. The itertools way is quite hard to understand IMHO and trying to hack lambdas to do this is plain silly. I’d try:

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

Overall, readability trumps compactness every time.


回答 8

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

No, you cannot put an assignment inside a lambda because of its own definition. If you work using functional programming, then you must assume that your values are not mutable.

One solution would be the following code:

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

如果您需要一个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()不会影响本地命名空间相同的方式来修改本地命名空间。

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__. Now that all my cautions against what you are trying to do is out of the way, we can get to an actual answer to your query.

If you really need to have your lambda to have some memory between calls, you can define it like:

f = lambda o, ns = {"flag":True}: [ns["flag"] or o.name, ns.__setitem__("flag", ns["flag"] and o.name)][0]

Then you just need to pass f to filter(). If you really need to, you can get back the value of flag with the following:

f.__defaults__[0]["flag"]

Alternatively, you can modify the global namespace by modifying the result of globals(). Unfortunately, you cannot modify the local namespace in the same way as modifying the result of locals() doesn’t affect the local namespace.


回答 10

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

You can use a bind function to use a pseudo multi-statement lambda. Then you can use a wrapper class for a Flag to enable assignment.

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

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

Kind of a messy workaround, but assignment in lambdas is illegal anyway, so it doesn’t really matter. You can use the builtin exec() function to run assignment from inside the lambda, such as this example:

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

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

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

检查以下示例代码:

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

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

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

希望它可以帮助

first , you dont need to use a local assigment for your job, just check the above answer

second, its simple to use locals() and globals() to got the variables table and then change the value

check this sample code:

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

if you need to change the add a global variable to your environ, try to replace locals() with globals()

python’s list comp is cool but most of the triditional project dont accept this(like flask :[)

hope it could help


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。