Python中lambdaexpression式的赋值
我有一个对象的列表,我想删除除了一个空的所有对象,使用filter
和lambda
expression式。
例如,如果input是:
[Object(name=""), Object(name="fake_name"), Object(name="")]
…那么输出应该是:
[Object(name=""), Object(name="fake_name")]
有没有办法给lambda
expression式添加一个赋值? 例如:
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 2中执行本地分配,作为列表parsing的副作用。
import sys say_hello = lambda: [ [None for message in ["Hello world"]], sys.stdout.write(message + "\n") ][-1] say_hello()
然而,在你的例子中不可能使用这个variables,因为你的variablesflag
位于外部范围,而不是lambda
的范围。 这与lambda
无关,这是Python 2中的一般行为.Python 3让你可以使用nonlocal
关键字来解决这个问题,但nonlocal
不能在lambda
里面使用,而Python 3会移除这个方面列表parsing的效果,所以这在Python 3中是不可能的。
有一个解决方法(见下文),但是当我们在这个话题上…
在某些情况下,您可以使用它来完成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
variables进行赋值,但是你可以使用函数来修改以前赋值的值。
例如, 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
。
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
expression式中的状态(除非滥用全局名称空间)。 但是,您可以使用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'] >>>
当然,你可以调整一下状态。 在这种情况下,它会过滤掉重复项,但是也可以使用a.count("")
来限制空白string。
不用说,你可以做到这一点,但你真的不应该这样做。 🙂
最后,你可以在纯Python lambda
做任何事情: http : //vanderwijk.info/blog/pure-lambda-calculus-python/
没有必要使用lambdaexpression式,当你可以删除所有的空字符时,如果input大小发生变化,就把它放回去:
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=""))
更新 :
[o for d in [{}] for o in lst if o.name != "" or d.setdefault("", o) == o]
或者使用filter
和lambda
:
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的散文后,至less这是我的印象。
我的猜测是,这也违背了Python中做任何事情的正确方法。
普通赋值( =
)在lambda
expression式中是不可能的,尽pipe可以用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__
这里解释。
如果不是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写得更好:
>>> filter(lambda L, blank_count=count(1): L or next(blank_count) == 1, a)
或者,只是一个简单的布尔值,没有任何import:
filter(lambda L, use_blank=iter([True]): L or next(use_blank, False), a)
迭代过程中跟踪状态的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))
总的来说,可读性胜过每一次紧凑。
如果你需要一个lambda来记住调用之间的状态,我会推荐一个在本地命名空间中声明的函数或者一个带有重载__call__
的类。 现在,我对所做的所有事情的注意事项都已经排除在外,我们可以为您的查询find实际的答案。
如果你真的需要让你的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()
的结果一样修改本地命名空间,不会影响本地命名空间。
您可以使用绑定函数来使用伪多语句lambda。 然后你可以使用一个包装类的标志来启用分配。
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)
TL; DR:当使用function性语言时,最好编写function性代码
正如很多人所指出的那样,在Python中lambdaexpression式是不允许的。 一般来说,当使用function性习语时,您最好以function性的方式思考,这意味着在任何可能的情况下都没有副作用,也没有任务。
这是使用lambda的function解决scheme。 为了清晰起见,我已经将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 != '')
你也可以用迭代器而不是列表来处理这个问题。 你也有一些不同的import。
from itertools import chain, islice, ifilter, ifilterfalse fn = lambda l, pred: chain(ifilter(pred, iter(l)), islice(ifilterfalse(pred, iter(l)), 1))
您可以随时对代码进行reoganize以减less语句的长度。
不,你不能在lambda中放置一个赋值,因为它有自己的定义。 如果你使用函数式编程,那么你必须假设你的值是不可变的。
一个解决scheme是以下代码:
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 )
首先,你不需要使用当地的工作分配,只需检查上面的答案
其次,它使用简单的locals()和globals()来获取variables表,然后改变它的值
检查这个示例代码:
print [locals().__setitem__('x', 'Hillo :]'), x][-1]
如果你需要改变添加一个全局variables到你的环境,尝试用globals()replacelocals ()
python的列表比较酷,但大部分的三项目不接受这个(像瓶:[)
希望它可以帮助