2013-04-11 8 views
49

Ich war auf der Suche durch heute meine Code-Basis und dies gefunden:Wie funktioniert dieses Lambda/Ertrag/Generator-Verständnis?

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

Es ein dict von Parametern zu nehmen scheint und sie in eine Liste von Parametern für einen Shell-Befehl aktiviert. Es sieht so aus, als würde er die Ausbeute innerhalb eines Generatorverständnisses nutzen, was ich für unmöglich hielt ...?

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

Wie funktioniert es?

+66

Dang. Sprechen Sie über nicht lesbaren Code. – BenDundee

+2

der lustigste Teil ist die 'Liste (umgekehrt (Liste (' Teil, um die '-o' Schalter rechts, obwohl – ch3ka

+5

Auch alle Lambdas hätte nur sein können' ((Lambda _: '-o') ((Ausbeute x)) für x in kvs) ' –

Antwort

47

Seit Python 2.5 ist yield <value> ein Ausdruck, keine Anweisung. Siehe PEP 342.

Der Code ist scheußlich und unnötig hässlich, aber es ist legal. Sein zentraler Trick ist die Verwendung von f((yield x)) innerhalb des Generatorausdrucks. Hier ist ein einfacheres Beispiel dafür, wie dies funktioniert:

>>> def f(val): 
...  return "Hi" 
>>> x = [1, 2, 3] 
>>> list(f((yield a)) for a in x) 
[1, 'Hi', 2, 'Hi', 3, 'Hi'] 

Grundsätzlich mit yield in dem Generator Ausdruck bewirkt, dass es zwei Werte für jeden Wert in der iterable Quelle erzeugen. Wenn der Generatorausdruck die Liste der Strings iteriert, gibt der yield x bei jeder Iteration zuerst eine Zeichenfolge aus der Liste aus. Der Zielausdruck des Gensxp ist f((yield x)), so dass für jeden Wert in der Liste das "Ergebnis" des Generatorausdrucks der Wert f((yield x)) ist. Aber f ignoriert nur sein Argument und gibt immer die Optionszeichenfolge zurück. Bei jedem Schritt durch den Generator gibt es also zuerst die Schlüsselwertfolge (z. B. "x=1") und dann "-o" aus. Die äußere list(reversed(list(...))) macht nur eine Liste von diesem Generator und kehrt dann um, so dass die "-o" s vor jeder Option statt nach kommen wird.

Es gibt jedoch keinen Grund, dies auf diese Weise zu tun. Es gibt eine Reihe viel besser lesbarer Alternativen. Vielleicht ist die deutlichste ist einfach:

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

Auch wenn Sie kurz und bündig mögen, „clever“ Code, könnte man immer noch nur tun

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

Die kvs Liste Verständnis selbst eine bizarre Mischung aus versucht, die Lesbarkeit ist und Unlesbarkeit. Es ist einfacher geschrieben:

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

Sie berücksichtigen sollen, eine „Intervention“ für die Anordnung wer auch immer diese in deiner Code-Basis.

+3

Looking in der Geschichte, es war einmal: 'Return-Liste (itertools.chain (* [['- o', v] für v in kvs]))'. Es ist unklar, warum es davon geändert wurde. – Dog

+1

@Dog Das einzige Änderung, die ich tun würde, um den Code in Ihrem Kommentar ist es zu verwenden, um zu vermeiden, "itertools.chain.from_iterable" Verwenden Sie das '*' (das kann teuer werden, wenn die Liste groß ist) ... – Bakuriu

19

Oh Gott. Im Grunde ist es läuft darauf hinaus, diese ,:

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

Also, wenn iteriert, TheGenerator Ausbeuten x (ein Mitglied der kvs) und dann der Rückgabewert von f, die immer -o, alle in einer Iteration über kvs. Was immer yield x zurückgibt und was an f übergeben wird, wird ignoriert.

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' 

Es gibt viele Möglichkeiten, dies viel einfacher zu tun, natürlich.Selbst mit dem ursprünglichen Doppel-Ausbeute-Trick könnte das ganze Ding

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