Modo Pythonic per combinare ciclo FOR e istruzione IF


266

So usare sia per i loop che per le istruzioni if ​​su righe separate, come:

>>> a = [2,3,4,5,6,7,8,9,0]
... xyz = [0,12,4,6,242,7,9]
... for x in xyz:
...     if x in a:
...         print(x)
0,4,6,7,9

E so di poter usare una comprensione dell'elenco per combinarli quando le dichiarazioni sono semplici, come ad esempio:

print([x for x in xyz if x in a])

Ma quello che non riesco a trovare è un buon esempio ovunque (da copiare e da cui imparare) che dimostra un complesso set di comandi (non solo "print x") che si verificano seguendo una combinazione di un ciclo for e alcune istruzioni if. Qualcosa che mi aspetterei sembra:

for x in xyz if x not in a:
    print(x...)

Non è questo il modo in cui Python dovrebbe funzionare?


23
È così ... non complicare troppo le cose cercando di semplificarle. Pythonic non significa evitare ogni forciclo e ifistruzione espliciti .
Felix Kling

2
È possibile utilizzare l'elenco generato nella comprensione dell'elenco in un ciclo for. Sembrerebbe un po 'il tuo ultimo esempio.
Jacob

Quindi, passando all'elaborazione, qual è il modo più veloce per combinare un ciclo for con un'istruzione if, se l'istruzione if esclude i valori che sono già stati abbinati e l'elenco cresce continuamente durante l'iterazione del ciclo for?
ChewyChunks

3
@Chewy, strutture dati adeguate renderanno il codice più veloce, non zucchero sintattico. Ad esempio, x in aè lento se aè un elenco.
Nick Dandoulakis,

1
Questo è Python, un linguaggio interpretato; perché qualcuno sta discutendo di quanto sia veloce il codice?
ArtOfWarfare

Risposte:


323

Puoi usare espressioni del generatore in questo modo:

gen = (x for x in xyz if x not in a)

for x in gen:
    print x

1
gen = (y for (x,y) in enumerate(xyz) if x not in a)ritorna >>> 12quando scrivo for x in gen: print x- quindi perché il comportamento imprevisto con enumerazione?
ChewyChunks

9
Blocchi possibili, ma non migliori dell'originale per e se.
Mike Graham,

1
@ChewyChunks. Funzionerebbe ma la chiamata all'enumerazione è ridondante.
Johnsyweb,

132
Mi manca molto il fatto che Python sia in grado di direfor x in xyz if x:
bgusach,

10
for x in (x for x in xyz if x not in a):funziona per me, ma perché non dovresti essere in grado di farlo for x in xyz if x not in a:, non sono sicuro ...
Matt Wenham,

34

Come da The Zen of Python (se ti stai chiedendo se il tuo codice è "Pythonic", questo è il posto dove andare):

  • Bello è meglio che brutto.
  • Esplicito è meglio che implicito.
  • Semplice è meglio di complesso.
  • Flat è meglio di nidificato.
  • La leggibilità conta.

Il modo Pythonic di ottenere i due s è:sorted intersectionset

>>> sorted(set(a).intersection(xyz))
[0, 4, 6, 7, 9]

O quegli elementi che sono xyzma non in a:

>>> sorted(set(xyz).difference(a))
[12, 242]

Ma per un ciclo più complicato potresti volerlo appiattire ripetendo un'espressione generatrice ben nota e / o chiamando una funzione ben denominata. Cercare di adattare tutto su una riga è raramente "Pythonic".


Aggiorna dopo ulteriori commenti sulla tua domanda e la risposta accettata

Non sono sicuro di cosa stai cercando di fare enumerate, ma se aè un dizionario, probabilmente vorrai usare i tasti, in questo modo:

>>> a = {
...     2: 'Turtle Doves',
...     3: 'French Hens',
...     4: 'Colly Birds',
...     5: 'Gold Rings',
...     6: 'Geese-a-Laying',
...     7: 'Swans-a-Swimming',
...     8: 'Maids-a-Milking',
...     9: 'Ladies Dancing',
...     0: 'Camel Books',
... }
>>>
>>> xyz = [0, 12, 4, 6, 242, 7, 9]
>>>
>>> known_things = sorted(set(a.iterkeys()).intersection(xyz))
>>> unknown_things = sorted(set(xyz).difference(a.iterkeys()))
>>>
>>> for thing in known_things:
...     print 'I know about', a[thing]
...
I know about Camel Books
I know about Colly Birds
I know about Geese-a-Laying
I know about Swans-a-Swimming
I know about Ladies Dancing
>>> print '...but...'
...but...
>>>
>>> for thing in unknown_things:
...     print "I don't know what happened on the {0}th day of Christmas".format(thing)
...
I don't know what happened on the 12th day of Christmas
I don't know what happened on the 242th day of Christmas

Sembra dai commenti qui sotto, dovrei studiare i generatori. Non li ho mai usati. Grazie. Un generatore è più veloce della combinazione equivalente di istruzioni FOR e IF? Ho anche usato set, ma a volte gli elementi ridondanti in un elenco sono informazioni che non posso scartare.
ChewyChunks

@ChewyChunks: i generatori non sono l'unico modo per essere Pythonic!
Johnsyweb,

3
@Johnsyweb, se hai intenzione di citare lo Zen di Python: "Dovrebbe esserci uno - e preferibilmente solo un - modo obsoleto di farlo."
Wooble,

@Wooble: ci dovrebbe essere. Ho citato quella sezione nella mia risposta a un'altra domanda nello stesso momento!
Johnsyweb,

18

Personalmente penso che questa sia la versione più bella:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]
for x in filter(lambda w: w in a, xyz):
  print x

modificare

se si desidera evitare di utilizzare lambda, è possibile utilizzare l'applicazione delle funzioni parziali e utilizzare il modulo operatore (che fornisce le funzioni della maggior parte degli operatori).

https://docs.python.org/2/library/operator.html#module-operator

from operator import contains
from functools import partial
print(list(filter(partial(contains, a), xyz)))

4
filter(a.__contains__, xyz). Di solito quando le persone usano lambda, hanno davvero bisogno di qualcosa di molto più semplice.
Veky,

Penso che tu abbia frainteso qualcosa. __contains__è un metodo come un altro, solo che è un metodo speciale , il che significa che può essere chiamato indirettamente da un operatore ( inin questo caso). Ma può anche essere chiamato direttamente, fa parte dell'API pubblica. I nomi privati ​​sono specificatamente definiti come aventi al massimo un carattere di sottolineatura finale, per fornire un'eccezione ai nomi di metodi speciali e sono soggetti alla modifica del nome quando si trovano in ambito lessicale nell'ambito della classe. Vedi docs.python.org/3/reference/datamodel.html#specialnames e docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky,

È certamente ok, ma due importazioni solo per poter fare riferimento a un metodo accessibile usando solo un attributo sembrano strane (gli operatori vengono solitamente usati quando la doppia spedizione è essenziale, ma inviene spedita singolarmente rispetto all'operando giusto). Inoltre, nota che operatoresporta anche il containsmetodo sotto il nome __contains__, quindi sicuramente non è un nome privato. Penso che dovrai solo imparare a convivere con il fatto che non ogni doppio carattere di sottolineatura significa "tenere lontano". : -]
Veky

Penso che le tue lambdanecessità di correzione includano not: lambda w: not w in a, xyz
javadba

Il filtro sembra più elegante, specialmente per condizioni complesse che diventerebbero funzioni definite anziché lambdas, forse nominare la funzione lambda aggiungerebbe un po 'di leggibilità, Il generatore sembra migliore quando gli elementi iterati sono una modifica delle voci dell'elenco
Khanis Rok

16

Di seguito è riportata una semplificazione / una riga dalla risposta accettata:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]

for x in (x for x in xyz if x not in a):
    print(x)

12
242

Si noti che è generatorstato mantenuto in linea . Questo è stato testato python2.7e python3.6 (notare le parentesi nella print;))


10

Probabilmente userei:

for x in xyz: 
    if x not in a:
        print x...

@KirillTitov Sì python è un linguaggio fondamentalmente non funzionale (si tratta di un codice puramente imperativo - e sono d'accordo con l'autore di questa risposta che è il modo in cui è configurato Python per essere scritto. Il tentativo di usare i funzionali porta a leggere o non- pythonicposso codificare funzionalmente in ogni altra lingua che uso (scala, kotlin, javascript, R, swift, ..) ma difficile / imbarazzante in python
javadba

9
a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]  
set(a) & set(xyz)  
set([0, 9, 4, 6, 7])

Molto Zen, @lazyr, ma non mi aiuterebbe a migliorare un complesso blocco di codice che dipende dall'iterare attraverso un elenco e ignorare gli elementi corrispondenti in un altro elenco. È più veloce trattare il primo elenco come un insieme e confrontare l'unione / differenza con un secondo elenco crescente "ignora"?
ChewyChunks

Prova questoimport time a = [2,3,4,5,6,7,8,9,0] xyz = [0,12,4,6,242,7,9] start = time.time() print (set(a) & set(xyz)) print time.time() - start
Kracekumar l'

@ChewyChunks se una delle liste cambia durante l'iterazione, probabilmente sarà più veloce controllare ogni elemento rispetto all'elenco ignora - tranne che dovresti renderlo un set ignore. Controllo per l'adesione in due set è molto veloce: if x in ignore: ....
Lauritz V. Thaulow,

@lazyr Ho appena riscritto il mio codice usando un set ignore su un elenco ignore. Sembra che il tempo di elaborazione sia molto più lento. (Per essere onesti stavo confrontando usando if set(a) - set(ignore) == set([]):quindi forse è per questo che è stato molto più lento del controllo dell'appartenenza. In futuro lo
proverò

5

Puoi usare anche i generatori , se le espressioni dei generatori diventano troppo coinvolte o complesse:

def gen():
    for x in xyz:
        if x in a:
            yield x

for x in gen():
    print x

Questo è un po 'più utile per me. Non ho mai guardato i generatori. Sembrano spaventosi (perché li ho visti in moduli che erano generalmente un dolore da usare).
ChewyChunks

2

Usa intersectionointersection_update

  • incrocio :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    ans = sorted(set(a).intersection(set(xyz)))
  • intersection_update :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    b = set(a)
    b.intersection_update(xyz)

    allora bè la tua risposta


2

Mi è piaciuta la risposta di Alex , perché un filtro è esattamente un se applicato a un elenco, quindi se vuoi esplorare un sottoinsieme di un elenco dato una condizione, questo sembra essere il modo più naturale

mylist = [1,2,3,4,5]
another_list = [2,3,4]

wanted = lambda x:x in another_list

for x in filter(wanted, mylist):
    print(x)

questo metodo è utile per la separazione delle preoccupazioni, se la funzione condizione cambia, l'unico codice con cui armeggiare è la funzione stessa

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

for x in filter(wanted, mylist):
    print(x)

Il metodo del generatore sembra migliore quando non vuoi membri dell'elenco, ma una modifica di detti membri, che sembra più adatta a un generatore

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.5 for x in mylist if wanted(x))

for x in generator:
    print(x)

Inoltre, i filtri funzionano con i generatori, sebbene in questo caso non sia efficiente

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.9 for x in mylist)

for x in filter(wanted, generator):
    print(x)

Ma ovviamente sarebbe comunque bello scrivere così:

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

# for x in filter(wanted, mylist):
for x in mylist if wanted(x):
    print(x)

0

Un modo semplice per trovare elementi comuni univoci degli elenchi aeb:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
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.