这个lambda / yield / generator理解是如何工作的?

我今天正在查看我的代码库,发现这个:

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

它似乎需要一个参数的字典,并把它们变成一个shell命令的参数列表。 它看起来像是在一个生成器理解中使用yield,我认为这是不可能的。

 >>> optionsToArgs({"x":1,"y":2,"z":3}) ['-o', 'z=3', '-o', 'x=1', '-o', 'y=2'] 

它是如何工作的?

从Python 2.5开始, yield <value>是一个expression式,而不是一个语句。 见PEP 342 。

代码是丑陋的,不必要的丑陋,但它是合法的。 它的核心技巧是在生成器expression式中使用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'] 

基本上,在生成器expression式中使用yield会导致它为源迭代器中的每个值生成两个值。 当生成器expression式遍历string列表时,每次迭代时, yield x首先从列表中产生一个string。 genexp的目标expression式是f((yield x)) ,因此对于列表中的每个值,生成器expression式的“结果”都是f((yield x)) 。 但是, f只是忽略了它的参数,总是返回选项string"-o" 。 因此,在通过生成器的每一步中,首先产生键值string(例如, "x=1" ),然后是"-o" 。 外部list(reversed(list(...)))只是从这个生成器中生成一个列表,然后反转它,这样"-o"就会出现在每个选项之前,而不是之后。

但是,没有理由这样做。 有很多更可读的select。 也许最明确的是:

 kvs = [...] # same list comprehension can be used for this part result = [] for keyval in kvs: result.append("-o") result.append(keyval) return result 

即使你喜欢简洁,“聪明”的代码,你仍然可以做到

 return sum([["-o", keyval] for keyval in kvs], []) 

kvs列表理解本身是一个尝试可读性和不可读性的奇怪组合。 这是更简单的写法:

 kvs = [str(optName) + separator + str(optValue) for optName, optValue in options.items()] 

你应该考虑安排一个“干预”,把这个放在你的代码库里。

天啊。 基本上,归结到这一点,:

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

所以当迭代时,生成器产生xkvs一个成员),然后f的返回值总是-o ,全部在一次迭代中超过kvs 。 无论yield x返回什么都传递给f被忽略。

等同于:

 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' 

当然,有很多方法可以简单得多。 即使是最初的双重收益的伎俩,整个事情可能已经

 return list(((lambda _: '-o')((yield x)) for x in kvs))[::-1]