2013-04-11 2 views
49

Я просматривал мой кодовую сегодня и нашел это:Как это понимание лямбда/выхода/генератора работает?

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

Кажется, взять Dict параметров и превратить их в список параметров для команды оболочки. Похоже, что он использует выход внутри понимания генератора, который, как я думал, будет невозможным ...?

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

Как это работает?

+66

Dang. Расскажите о нечитаемом коде. – BenDundee

+2

самой забавной частью является «список (обратный (список (' part, чтобы получить '-o' переключатели вправо, хотя – ch3ka

+5

Также все лямбды могли быть просто' ((lambda _: '-o') ((yield x)) для x в kvs) ' –

ответ

47

Поскольку Python 2.5, yield <value> - выражение, а не утверждение. См. PEP 342.

Код ужасно и излишне уродливый, но это законно. Его центральный трюк использует 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'] 

В принципе, используя yield в выражении генератора заставляет его производить два значения для каждого значения в источнике итерации. Поскольку выражение генератора итератируется над списком строк, на каждой итерации yield x сначала выводит строку из списка. Целевая экспрессия гена xp равна f((yield x)), поэтому для каждого значения в списке «результатом» выражения генератора является значение f((yield x)). Но f просто игнорирует свой аргумент и всегда возвращает строку опций "-o". Таким образом, каждый шаг через генератор дает сначала строку значений ключа (например, "x=1"), затем "-o". Внешний list(reversed(list(...))) просто делает список из этого генератора, а затем меняет его так, что "-o" s будет использоваться перед каждым вариантом, а не после него.

Однако нет причин для этого. Существует ряд гораздо более читаемых альтернатив. Пожалуй, наиболее явным просто:

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 список понимание представляет собой причудливое сочетание попытки читаемости и unreadability. Это более просто написано:

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

Вы должны рассмотреть вопрос организации в «вмешательство» для тех, кто это в вашем коде.

+3

Looking в истории это был: 'return list (itertools.chain (* [['- o', v] для v в kvs]))'. Непонятно, почему это было изменено. – Dog

+1

@Dog Единственный изменение, которое я сделал бы с кодом в вашем комментарии, чтобы использовать 'itertools.chain.from_iterable', чтобы избежать используя '*' (который может стать дорогим, если список большой) ... – Bakuriu

19

О, боже. В основном, это сводится к тому ,:

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

Так что, когда итерации, thegenerator дает 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] 
Смежные вопросы