2013-04-11 13 views
49

stavo guardando attraverso il mio codice di base di oggi e trovato questo:Come funziona questa comprensione lambda/resa/generatore?

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

Sembra prendere un dict di parametri e li trasformano in un elenco di parametri per un comando di shell. Sembra che stia usando la resa all'interno di una comprensione del generatore, che pensavo sarebbe stata impossibile ...?

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

Come funziona?

+66

Dang. Parla di codice illeggibile. – BenDundee

+2

la parte più divertente è la 'lista (invertita (lista (' parte per ottenere le opzioni '-o' a destra, anche se – ch3ka

+5

Anche tutti i lambda potevano essere stati semplicemente' ((lambda _: '-o') ((resa x)) per x in kvs) ' –

risposta

47

Poiché Python 2.5, yield <value> è un'espressione, non un'istruzione. Vedi PEP 342.

Il codice è orribilmente e inutilmente brutto, ma è legale. Il suo trucco principale è usare f((yield x)) all'interno dell'espressione del generatore. Ecco un esempio più semplice di come funziona:

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

In sostanza, utilizzando yield nell'espressione generatore induce a produrre due valori per ogni valore nella sorgente iterabile. Poiché l'espressione del generatore esegue iterazioni sull'elenco di stringhe, a ogni iterazione, lo yield x innanzitutto fornisce una stringa dall'elenco. L'espressione target del genexp è f((yield x)), quindi per ogni valore nell'elenco, il "risultato" dell'espressione del generatore è il valore di f((yield x)). Ma f ignora semplicemente il suo argomento e restituisce sempre la stringa di opzioni "-o". Quindi, in ogni passaggio attraverso il generatore, produce prima la stringa valore-chiave (ad es., "x=1"), quindi "-o". L'esterno list(reversed(list(...))) crea solo un elenco da questo generatore e quindi lo inverte in modo tale che lo "-o" s venga prima di ogni opzione anziché dopo.

Tuttavia, non c'è motivo di farlo in questo modo. Esistono numerose alternative molto più leggibili. Forse il più esplicito è semplicemente:

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

Anche se ti piace terso, codice "intelligente", si potrebbe ancora basta fare

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

Il kvs di lista è di per sé un bizzarro mix di tentato leggibilità e illeggibilità. E 'scritto più semplicemente:

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

Si dovrebbe prendere in considerazione l'organizzazione di un "intervento" per chi mettere questo nel tuo codebase.

+3

nella cronologia, era: 'return list (itertools.chain (* [['- o', v] per v in kvs]))'. Non è chiaro il motivo per cui è stato modificato da questo. – Dog

+1

@Dog L'unico cambiare vorrei fare al codice nel tuo commento è di usare 'itertools.chain.from_iterable' per evitare usando il '*' (che può diventare costoso se la lista è grande) ... – Bakuriu

19

Oh dio. Fondamentalmente, si riduce a questo ,:

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

Così quando iterata, thegenerator rendimenti x (un socio di kvs) e poi il valore restituito f, che è sempre -o, tutti in un'iterazione su kvs. Qualunque sia il rendimento yield x e ciò che viene passato a f viene ignorato.

equivalenti:

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' 

Ci sono molti modi per fare questo molto più semplice, ovviamente.Anche con l'originale trucco a doppio rendimento, l'intera cosa avrebbe potuto essere

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