Pythonische Methode zum Kombinieren von FOR-Schleife und IF-Anweisung

266

Ich weiß, wie man sowohl for-Schleifen als auch if-Anweisungen in separaten Zeilen verwendet, wie zum Beispiel:

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

Und ich weiß, dass ich ein Listenverständnis verwenden kann, um diese zu kombinieren, wenn die Aussagen einfach sind, wie zum Beispiel:

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

Was ich jedoch nicht finden kann, ist ein gutes Beispiel (zum Kopieren und Lernen), das einen komplexen Satz von Befehlen (nicht nur "print x") zeigt, die nach einer Kombination aus einer for-Schleife und einigen if-Anweisungen auftreten. Etwas, das ich erwarten würde, sieht aus wie:

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

Ist das nicht die Art und Weise, wie Python funktionieren soll?

ChewyChunks
quelle
23
So ist es ... überkomplizieren Sie die Dinge nicht, indem Sie versuchen, sie zu vereinfachen. Pythonic bedeutet nicht, jede explizite forSchleife und ifAussage zu vermeiden .
Felix Kling
2
Sie können die in Ihrem Listenverständnis generierte Liste in einer for-Schleife verwenden. Das würde etwas wie Ihr letztes Beispiel aussehen.
Jacob
Was ist also der schnellste Weg, eine for-Schleife mit einer if-Anweisung zu kombinieren, wenn die if-Anweisung Werte ausschließt, die bereits übereinstimmen, und die Liste während der Iteration der for-Schleife kontinuierlich wächst?
ChewyChunks
3
@Chewy, richtige Datenstrukturen machen den Code schneller, nicht syntaktischen Zucker. x in aIst zum Beispiel langsam, wenn aes sich um eine Liste handelt.
Nick Dandoulakis
1
Dies ist Python, eine interpretierte Sprache; Warum diskutiert jemand, wie schnell Code überhaupt ist?
ArtOfWarfare

Antworten:

322

Sie können Generatorausdrücke wie folgt verwenden:

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

for x in gen:
    print x
Kugel
quelle
1
gen = (y for (x,y) in enumerate(xyz) if x not in a)Gibt >>> zurück, 12wenn ich tippe for x in gen: print x- warum also das unerwartete Verhalten bei der Aufzählung?
ChewyChunks
9
Möglich, aber nicht schöner als das Original für und wenn Blöcke.
Mike Graham
1
@ChewyChunks. Das würde funktionieren, aber der Aufruf zum Aufzählen ist überflüssig.
Johnsyweb
131
Ich vermisse es wirklich in Python, sagen zu könnenfor x in xyz if x:
bgusach
10
for x in (x for x in xyz if x not in a):funktioniert für mich, aber warum Sie nicht einfach dazu in der Lage sein sollten for x in xyz if x not in a:, bin ich mir nicht sicher ...
Matt Wenham
34

Wie pro The Zen of Python (wenn Sie sich fragen , ob Ihr Code „Pythonic“, das ist der Ort zu gehen):

  • Schön ist besser als hässlich.
  • Explizit ist besser als implizit.
  • Einfach ist besser als komplex.
  • Wohnung ist besser als verschachtelt.
  • Lesbarkeit zählt.

Der pythonische Weg, das von zwei s zu bekommen, ist:sorted intersectionset

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

Oder jene Elemente, die xyzaber nicht in a:

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

Für eine kompliziertere Schleife möchten Sie sie möglicherweise reduzieren, indem Sie einen gut benannten Generatorausdruck durchlaufen und / oder eine gut benannte Funktion aufrufen. Der Versuch, alles in eine Zeile zu bringen, ist selten "Pythonic".


Aktualisieren Sie nach zusätzlichen Kommentaren zu Ihrer Frage und der akzeptierten Antwort

Ich bin nicht sicher, womit Sie versuchen enumerate, aber wenn aes sich um ein Wörterbuch handelt, möchten Sie wahrscheinlich die Schlüssel wie folgt verwenden:

>>> 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
Johnsyweb
quelle
Klingt nach den Kommentaren unten, ich sollte mich mit Generatoren befassen. Ich habe sie nie benutzt. Vielen Dank. Ist ein Generator schneller als die entsprechende Kombination von FOR- und IF-Anweisungen? Ich habe auch Sets verwendet, aber manchmal sind redundante Elemente in einer Liste Informationen, die ich nicht verwerfen kann.
ChewyChunks
@ChewyChunks: Generatoren sind nicht die einzige Möglichkeit, Pythonic zu sein!
Johnsyweb
3
@ Johnsyweb, wenn Sie das Zen von Python zitieren wollen: "Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun."
Wooble
@Wooble: Das sollte es. Ich habe diesen Abschnitt ungefähr zur gleichen Zeit in meiner Antwort auf eine andere Frage zitiert !
Johnsyweb
18

Ich persönlich denke, dies ist die schönste Version:

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

Bearbeiten

Wenn Sie die Verwendung von Lambda vermeiden möchten, können Sie die Teilfunktionsanwendung und das Operatormodul (das die Funktionen der meisten Operatoren bereitstellt) verwenden.

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)))
Alex
quelle
4
filter(a.__contains__, xyz). Wenn Menschen Lambda verwenden, brauchen sie normalerweise etwas viel Einfacheres.
Veky
Ich denke, Sie haben etwas falsch verstanden. __contains__ist eine Methode wie jede andere, nur ist es eine spezielle Methode, dh sie kann indirekt von einem Operator ( inin diesem Fall) aufgerufen werden . Es kann aber auch direkt aufgerufen werden, es ist Teil der öffentlichen API. Private Namen werden speziell so definiert, dass sie höchstens einen abschließenden Unterstrich haben, um eine Ausnahme für spezielle Methodennamen zu bilden - und sie unterliegen der Namensverfälschung, wenn sie lexikalisch in Klassenbereichen verwendet werden. Siehe docs.python.org/3/reference/datamodel.html#specialnames und docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky
Es ist sicherlich in Ordnung, aber zwei Importe, nur um auf eine Methode verweisen zu können, auf die nur mit einem Attribut inzugegriffen werden kann, scheinen seltsam (Operatoren werden normalerweise verwendet, wenn ein doppelter Versand erforderlich ist, aber einfach mit dem richtigen Operanden versendet wird ). Beachten Sie außerdem, dass operatorauch die containsMethode unter dem Namen exportiert wird __contains__, sodass es sich sicherlich nicht um einen privaten Namen handelt. Ich denke, Sie müssen nur lernen, damit zu leben, dass nicht jeder doppelte Unterstrich "fernhalten" bedeutet. : -]
Veky
Ich denke, Ihre lambdaBedürfnisse müssen notlambda w: not w in a, xyz
behoben werden
Der Filter wirkt eleganter, insbesondere bei komplexen Bedingungen, bei denen anstelle von Lambdas definierte Funktionen verwendet werden. Vielleicht würde die Benennung der Lambda-Funktion die Lesbarkeit
verbessern.
16

Das Folgende ist eine Vereinfachung / ein Liner aus der akzeptierten Antwort:

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

Beachten Sie, dass das inlinegenerator gehalten wurde . Dies wurde am und getestet (beachten Sie die Parens in der ;))python2.7python3.6print

Javadba
quelle
10

Ich würde wahrscheinlich verwenden:

for x in xyz: 
    if x not in a:
        print x...
Wim Feijen
quelle
@KirillTitov Ja, Python ist eine grundsätzlich nicht funktionierende Sprache (dies ist eine rein zwingende Kodierung - und ich stimme dem Autor dieser Antwort zu, dass Python so eingerichtet ist, dass es geschrieben werden soll. Der Versuch, Funktionen zu verwenden, führt zu schlechtem Lesen oder Nicht-Lesen pythonicErgebnisse. Ich kann funktional in jeder anderen Sprache codieren, die ich benutze (Scala, Kotlin, Javascript, R, Swift, ..), aber schwierig / umständlich 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])
Kracekumar
quelle
Sehr Zen, @lazyr, aber würde mir nicht helfen, einen komplexen Codeblock zu verbessern, der davon abhängt, eine Liste zu durchlaufen und übereinstimmende Elemente in einer anderen Liste zu ignorieren. Ist es schneller, die erste Liste als Satz zu behandeln und die Vereinigung / Differenz mit einer zweiten, wachsenden "Ignorier" -Liste zu vergleichen?
ChewyChunks
Versuchen Sie diesimport 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
@ChewyChunks Wenn sich eine der Listen während der Iteration ändert, ist es wahrscheinlich schneller, jedes Element mit der Ignorierliste zu vergleichen - außer Sie sollten es zu einem Ignoriersatz machen. Die Überprüfung der Mitgliedschaft in Sets ist sehr schnell : if x in ignore: ....
Lauritz V. Thaulow
@lazyr Ich habe gerade meinen Code mit einem Ignoriersatz über einer Ignorierliste umgeschrieben . Scheint die Zeit viel langsamer zu verarbeiten. (Um fair zu sein, ich habe mit verglichen, if set(a) - set(ignore) == set([]):vielleicht war es deshalb viel langsamer als die Überprüfung der Mitgliedschaft. Ich werde dies in Zukunft an einem viel einfacheren Beispiel erneut testen als das, was ich schreibe.
ChewyChunks
5

Sie können auch Generatoren verwenden , wenn Generatorausdrücke zu kompliziert oder zu komplex werden:

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

for x in gen():
    print x
Lauritz V. Thaulow
quelle
Das ist etwas nützlicher für mich. Ich habe noch nie Generatoren angeschaut. Sie klingen beängstigend (weil ich sie in Modulen gesehen habe, deren Verwendung im Allgemeinen schmerzhaft war).
ChewyChunks
2

Verwenden Sie intersectionoderintersection_update

  • Kreuzung :

    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)

    dann bist deine Antwort

Chung-Yen Hung
quelle
2

Ich mochte Alex 'Antwort , weil ein Filter genau dann eine Liste ist, wenn er auf eine Liste angewendet wird. Wenn Sie also eine Teilmenge einer Liste unter bestimmten Bedingungen untersuchen möchten, scheint dies der natürlichste Weg zu sein

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)

Diese Methode ist nützlich für die Trennung von Bedenken. Wenn sich die Bedingungsfunktion ändert, ist der einzige Code, mit dem Sie herumspielen können, die Funktion selbst

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

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

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

Die Generatormethode scheint besser zu sein, wenn Sie keine Mitglieder der Liste möchten, sondern eine Modifikation dieser Mitglieder, die für einen Generator besser geeignet zu sein scheint

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)

Filter funktionieren auch mit Generatoren, obwohl dies in diesem Fall nicht effizient ist

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)

Aber natürlich wäre es trotzdem schön, so zu schreiben:

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)
Khanis Rok
quelle
0

Ein einfacher Weg, um eindeutige gemeinsame Elemente der Listen a und b zu finden:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
peawormsworth
quelle