Python 这个 lambda/yield/generator 理解是如何工作的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15955948/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How does this lambda/yield/generator comprehension work?
提问by Dog
I was looking through my codebase today and found this:
我今天浏览了我的代码库,发现了这个:
def optionsToArgs(options, separator='='):
kvs = [
(
"%(option)s%(separator)s%(value)s" %
{'option' : str(k), 'separator' : separator, 'value' : str(v)}
) for k, v in options.items()
]
return list(
reversed(
list(
(lambda l, t:
(lambda f:
(f((yield x)) for x in l)
)(lambda _: t)
)(kvs, '-o')
)
)
)
It seems to take a dict of parameters and turn them into a list of parameters for a shell command. It looks like it's using yield inside a generator comprehension, which I thought would be impossible...?
它似乎需要一个参数字典并将它们转换为 shell 命令的参数列表。看起来它在生成器理解中使用了 yield ,我认为这是不可能的......?
>>> optionsToArgs({"x":1,"y":2,"z":3})
['-o', 'z=3', '-o', 'x=1', '-o', 'y=2']
How does it work?
它是如何工作的?
采纳答案by BrenBarn
Since Python 2.5, yield <value>is an expression, not a statement. See PEP 342.
从 Python 2.5 开始,,yield <value>是一个表达式,而不是一个语句。参见PEP 342。
The code is hideously and unnecessarily ugly, but it's legal. Its central trick is using f((yield x))inside the generator expression. Here's a simpler example of how this works:
该代码非常丑陋且不必要地丑陋,但它是合法的。它的核心技巧是f((yield x))在生成器表达式中使用。这是一个更简单的例子,说明它是如何工作的:
>>> def f(val):
... return "Hi"
>>> x = [1, 2, 3]
>>> list(f((yield a)) for a in x)
[1, 'Hi', 2, 'Hi', 3, 'Hi']
Basically, using yieldin the generator expression causes it to produce two values for every value in the source iterable. As the generator expression iterates over the list of strings, on each iteration, the yield xfirst yields a string from the list. The target expression of the genexp is f((yield x)), so for every value in the list, the "result" of the generator expression is the value of f((yield x)). But fjust ignores its argument and always returns the option string "-o". So on every step through the generator, it yields first the key-value string (e.g., "x=1"), then "-o". The outer list(reversed(list(...)))just makes a list out of this generator and then reverses it so that the "-o"s will come before each option instead of after.
基本上,yield在生成器表达式中使用会导致它为源迭代中的每个值生成两个值。当生成器表达式遍历字符串列表时,在每次迭代中,yield x第一个从列表中生成一个字符串。genexp 的目标表达式是f((yield x)),因此对于列表中的每个值,生成器表达式的“结果”是 的值f((yield x))。但f只是忽略它的参数并始终返回选项 string "-o"。因此,在生成器的每一步中,它首先生成键值字符串(例如,"x=1"),然后是"-o"。外部list(reversed(list(...)))只是从这个生成器中创建一个列表,然后将其反转,以便"-o"s 出现在每个选项之前而不是之后。
However, there is no reason to do it this way. There are a number of much more readable alternatives. Perhaps the most explicit is simply:
但是,没有理由这样做。有许多更具可读性的替代方案。也许最明确的就是:
kvs = [...] # same list comprehension can be used for this part
result = []
for keyval in kvs:
result.append("-o")
result.append(keyval)
return result
Even if you like terse, "clever" code, you could still just do
即使你喜欢简洁、“聪明”的代码,你仍然可以这样做
return sum([["-o", keyval] for keyval in kvs], [])
The kvslist comprehension itself is a bizarre mix of attempted readability and unreadability. It is more simply written:
该kvs列表理解本身就是试图可读性和不可读性的一个奇怪的组合。写得更简单:
kvs = [str(optName) + separator + str(optValue) for optName, optValue in options.items()]
You should consider arranging an "intervention" for whoever put this in your codebase.
您应该考虑为将其放入代码库的任何人安排“干预”。
回答by Pavel Anossov
Oh god. Basically, it boils down to this,:
天啊。基本上,它归结为:
def f(_): # I'm the lambda _: t
return '-o'
def thegenerator(): # I'm (f((yield x)) for x in l)
for x in kvs:
yield f((yield x))
So when iterated over, thegenerator yields x(a member of kvs) and then the return value of f, which is always -o, all in one iteration over kvs. Whatever yield xreturns and what gets passed to fis ignored.
因此,当迭代时,生成器产生x( 的成员kvs),然后返回 的值f(始终为 )-o,所有这些都在一次迭代中完成kvs。无论yield x返回什么以及传递给什么都f被忽略。
Equivalents:
等价物:
def thegenerator(): # I'm (f((yield x)) for x in l)
for x in kvs:
whatever = (yield x)
yield f(whatever)
def thegenerator(): # I'm (f((yield x)) for x in l)
for x in kvs:
yield x
yield f(None)
def thegenerator(): # I'm (f((yield x)) for x in l)
for x in kvs:
yield x
yield '-o'
There are lots of ways to do this much simpler, of course. Even with the original double-yield trick, the entire thing could've been
当然,有很多方法可以更简单地做到这一点。即使使用最初的双倍收益技巧,整个事情也可能是
return list(((lambda _: '-o')((yield x)) for x in kvs))[::-1]

