È Pythonic usare la comprensione delle liste solo per gli effetti collaterali?


108

Pensa a una funzione che sto chiamando per i suoi effetti collaterali, non restituire valori (come la stampa sullo schermo, l'aggiornamento della GUI, la stampa su un file, ecc.).

def fun_with_side_effects(x):
    ...side effects...
    return y

Ora, è Pythonic usare le liste di comprensione per chiamare questa funzione:

[fun_with_side_effects(x) for x in y if (...conditions...)]

Nota che non salvo l'elenco da nessuna parte

O dovrei chiamare questa funzione in questo modo:

for x in y:
    if (...conditions...):
        fun_with_side_effects(x)

Quale è meglio e perché?


6
questo è al limite, ma probabilmente sarai più contrario che nel supporto. Ho intenzione di lasciare questo: ^)
jcomeau_ictx

6
Questa è una scelta facile. La leggibilità conta: fallo nel secondo modo. Se non riesci a inserire 2 linee extra sullo schermo, ottieni un monitor più grande :)
John La Rooy

1
La comprensione della lista non è pitonica poiché viola "l'esplicito è meglio dell'implicito": stai nascondendo un ciclo in un costrutto diverso.
Fred Foo

3
@larsmans: se solo GvR se ne fosse reso conto quando ha introdotto le liste di comprensione in primo luogo!
Steve Jessop

2
@larsmans, Steve Jessop, penso che non sia corretto concepire una lista di comprensione come un ciclo. Può essere implementato come un ciclo, ma lo scopo di costrutti come questo è di operare su dati aggregati in modo funzionale e (concettualmente) parallelo. Se c'è un problema con la sintassi, è che for ... inviene utilizzato in entrambi i casi, portando a domande come questa!
mittente

Risposte:


84

È molto anti-pitonico farlo, e qualsiasi Pythonista esperto ti darà l'inferno. L'elenco intermedio viene gettato via dopo essere stato creato e potrebbe essere potenzialmente molto, molto grande e quindi costoso da creare.


5
Quindi quale sarebbe un modo più pitonico?
Joachim Sauer

6
Quello che non tiene l'elenco in giro; cioè qualche variante del secondo modo (sono stato conosciuto per usare un genex in forprecedenza, per sbarazzarsi del if).
Ignacio Vazquez-Abrams

6
@Joachim Sauer: Esempio 2 sopra. Un ciclo corretto, esplicito, non comprensivo di elenchi. Esplicito. Chiaro. Ovvio.
S.Lott

31

Non dovresti usare una lista di comprensione, perché come hanno detto le persone, questo creerà un grande elenco temporaneo che non ti serve. I due metodi seguenti sono equivalenti:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

con la definizione di consumedalla itertoolspagina man:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

Naturalmente, quest'ultimo è più chiaro e più facile da capire.


@ Paul: Penso che dovrebbe essere. E in effetti puoi, anche se mappotresti non essere così intuitivo se non hai mai fatto prima la programmazione funzionale.
Katriel

4
Non sono sicuro che sia particolarmente idiomatico. Non c'è alcun vantaggio rispetto all'utilizzo del ciclo esplicito.
Marcin

1
La soluzione èconsume = collections.deque(maxlen=0).extend
PaulMcG

24

Le comprensioni degli elenchi servono per creare elenchi. E a meno che non si sta effettivamente creando una lista, è necessario non utilizzare list comprehension.

Quindi avrei scelto la seconda opzione, semplicemente iterando l'elenco e quindi chiamare la funzione quando si applicano le condizioni.


6
Vorrei andare ancora oltre e affermare che gli effetti collaterali all'interno di una comprensione dell'elenco sono insoliti, inaspettati e quindi malvagi, anche se stai usando l'elenco risultante quando hai finito.
Mark Ransom

11

Il secondo è meglio.

Pensa alla persona che avrebbe bisogno di capire il tuo codice. Puoi ottenere un cattivo karma facilmente con il primo :)

Puoi andare a metà tra i due usando filter (). Considera l'esempio:

y=[1,2,3,4,5,6]
def func(x):
    print "call with %r"%x

for x in filter(lambda x: x>3, y):
    func(x)

10
Il tuo lambda è molto meglio scritto come lambda x : x > 3.
PaulMcG

Non hai nemmeno bisogno del filtro. Basta mettere un generatore di espressione in parentesi qui: for el in (x for x in y if x > 3):. ele xpuò avere lo stesso nome, ma ciò potrebbe confondere le persone.
Omnifarious

3

Dipende dal tuo obiettivo.

Se stai cercando di eseguire alcune operazioni su ogni oggetto in un elenco, è necessario adottare il secondo approccio.

Se stai cercando di generare un elenco da un altro elenco, puoi utilizzare la comprensione dell'elenco.

Esplicito è meglio che implicito. Semplice è meglio che complesso. (Python Zen)


0

Tu puoi fare

for z in (fun_with_side_effects(x) for x in y if (...conditions...)): pass

ma non è molto carino.


-1

Usare una comprensione delle liste per i suoi effetti collaterali è brutto, non pitonico, inefficiente e non lo farei. Io forinvece userei un loop, perché un forloop segnala uno stile procedurale in cui gli effetti collaterali sono importanti.

Ma, se insisti assolutamente nell'usare una comprensione dell'elenco per i suoi effetti collaterali, dovresti evitare l'inefficienza usando invece un'espressione generatrice. Se insisti assolutamente su questo stile, esegui uno di questi due:

any(fun_with_side_effects(x) and False for x in y if (...conditions...))

o:

all(fun_with_side_effects(x) or True for x in y if (...conditions...))

Queste sono espressioni del generatore e non generano un elenco casuale che viene buttato fuori. Penso che il allmodulo sia forse leggermente più chiaro, anche se penso che entrambi siano confusi e non dovrebbero essere usati.

Penso che questo sia brutto e in realtà non lo farei in codice. Ma se insisti a implementare i tuoi loop in questo modo, è così che lo farei.

Tendo a ritenere che le comprensioni degli elenchi e il loro genere dovrebbero segnalare un tentativo di utilizzare qualcosa che assomigli almeno vagamente a uno stile funzionale. Mettere cose con effetti collaterali che infrangono questo presupposto farà sì che le persone debbano leggere il tuo codice più attentamente, e penso che sia una brutta cosa.


Cosa succede se fun_with_side_effectsrestituisce True?
Katriel

7
Penso che questa cura sia peggiore della malattia: itertools.consume è molto più pulito.
PaulMcG

@PaulMcG - itertools.consumenon esiste più, probabilmente perché usare le comprensioni con effetti collaterali è brutto.
Omnifarious

1
Si scopre che mi sbagliavo e non è mai esistito come metodo in stdlib. Si tratta di una ricetta in itertools documentazione: docs.python.org/3/library/...
PaulMcG
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.