这个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))
所以当迭代时,生成器产生x
( kvs
一个成员),然后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]