Ist es pythonisch, Listenverständnisse nur für Nebenwirkungen zu verwenden?

108

Denken Sie an eine Funktion, die ich wegen ihrer Nebenwirkungen aufrufe, und geben Sie keine Werte zurück (z. B. Drucken auf dem Bildschirm, Aktualisieren der Benutzeroberfläche, Drucken in eine Datei usw.).

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

Ist es nun pythonisch , Listenverständnisse zu verwenden, um diese Funktion aufzurufen:

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

Beachten Sie, dass ich die Liste nirgendwo speichere

Oder sollte ich diese Funktion so nennen:

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

Was ist besser und warum?

Sinan
quelle
6
Dies ist eine Grenzlinie, aber Sie werden wahrscheinlich mehr Widerstand leisten als Unterstützung. Ich werde dieses hier
aussetzen
6
Dies ist eine einfache Wahl. Lesbarkeit zählt - machen Sie es auf die zweite Weise. Wenn Sie nicht 2 zusätzliche Zeilen auf Ihrem Bildschirm einfügen können, erhalten Sie einen größeren Monitor :)
John La Rooy
1
Das Listenverständnis ist unpythonisch, da es gegen "explizit ist besser als implizit" verstößt - Sie verstecken eine Schleife in einem anderen Konstrukt.
Fred Foo
3
@larsmans: Wenn nur GvR das erkannt hätte, als er das Listenverständnis überhaupt eingeführt hat!
Steve Jessop
2
@larsmans, Steve Jessop, ich denke, es ist falsch, ein Listenverständnis als Schleife zu verstehen. Es kann durchaus als Schleife implementiert werden, aber der Sinn solcher Konstrukte besteht darin, aggregierte Daten auf funktionale und (konzeptionell) parallele Weise zu bearbeiten. Wenn es ein Problem mit der Syntax gibt, for ... inwird es in beiden Fällen verwendet - was zu Fragen wie dieser führt!
senderle

Antworten:

84

Es ist sehr anti-pythonisch, dies zu tun, und jeder erfahrene Pythonista wird Ihnen die Hölle darüber geben. Die Zwischenliste wird nach ihrer Erstellung weggeworfen und kann möglicherweise sehr, sehr groß und daher teuer in der Erstellung sein.

Ignacio Vazquez-Abrams
quelle
5
Was wäre also ein pythonischerer Weg?
Joachim Sauer
6
Derjenige, der die Liste nicht führt; dh eine Variante des zweiten Weges (es ist bekannt, dass ich vorher einen Genex verwendet habe for, um den loszuwerden if).
Ignacio Vazquez-Abrams
6
@ Joachim Sauer: Beispiel 2 oben. Eine richtige, explizite Schleife ohne Listenverständnis. Explizit. Klar. Offensichtlich.
S.Lott
31

Sie sollten kein Listenverständnis verwenden , da dies, wie bereits erwähnt, eine große temporäre Liste erstellt, die Sie nicht benötigen. Die folgenden zwei Methoden sind äquivalent:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

mit der Definition von consumeaus der itertoolsManpage:

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)

Letzteres ist natürlich klarer und leichter zu verstehen.

Katriel
quelle
@ Paul: Ich denke es sollte sein. Und in der Tat können Sie, obwohl mapmöglicherweise nicht so intuitiv, wenn man noch keine funktionale Programmierung durchgeführt hat.
Katriel
4
Ich bin mir nicht sicher, ob dies besonders idiomatisch ist. Es gibt keinen Vorteil gegenüber der Verwendung der expliziten Schleife.
Marcin
1
Lösung istconsume = collections.deque(maxlen=0).extend
PaulMcG
24

Listenverständnisse dienen zum Erstellen von Listen. Und wenn Sie keine Liste erstellen, sollten Sie kein Listenverständnis verwenden.

Also würde ich mich für die zweite Option entscheiden, einfach die Liste durchlaufen und dann die Funktion aufrufen, wenn die Bedingungen zutreffen.

Ikke
quelle
6
Ich würde noch weiter gehen und feststellen, dass Nebenwirkungen innerhalb eines Listenverständnisses ungewöhnlich, unerwartet und daher böse sind, selbst wenn Sie die resultierende Liste verwenden, wenn Sie fertig sind.
Mark Ransom
11

Zweitens ist besser.

Denken Sie an die Person, die Ihren Code verstehen müsste. Mit dem ersten kannst du leicht schlechtes Karma bekommen :)

Sie können mit filter () zwischen den beiden wechseln. Betrachten Sie das Beispiel:

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)
user237419
quelle
10
Dein Lambda ist viel besser geschrieben als lambda x : x > 3.
PaulMcG
Sie brauchen nicht einmal Filter. Fügen Sie hier einfach einen Generatorausdruck in parens ein : for el in (x for x in y if x > 3):. elund xkann den gleichen Namen haben, aber das könnte die Leute verwirren.
Omnifarious
3

Kommt auf dein Ziel an.

Wenn Sie versuchen, für jedes Objekt in einer Liste eine Operation auszuführen, sollte der zweite Ansatz angewendet werden.

Wenn Sie versuchen, eine Liste aus einer anderen Liste zu generieren, können Sie das Listenverständnis verwenden.

Explizit ist besser als implizit. Einfach ist besser als komplex. (Python Zen)

rubayeet
quelle
0

Du kannst tun

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

aber es ist nicht sehr hübsch.

sigs
quelle
-1

Die Verwendung eines Listenverständnisses für seine Nebenwirkungen ist hässlich, nicht pythonisch, ineffizient, und ich würde es nicht tun. Ich würde forstattdessen eine Schleife verwenden, da eine forSchleife einen prozeduralen Stil signalisiert, bei dem Nebenwirkungen wichtig sind.

Wenn Sie jedoch unbedingt darauf bestehen, ein Listenverständnis für seine Nebenwirkungen zu verwenden, sollten Sie die Ineffizienz vermeiden, indem Sie stattdessen einen Generatorausdruck verwenden. Wenn Sie absolut auf diesem Stil bestehen, machen Sie einen der beiden folgenden Schritte:

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

oder:

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

Dies sind Generatorausdrücke, und sie generieren keine zufällige Liste, die weggeworfen wird. Ich denke, die allForm ist vielleicht etwas klarer, obwohl ich denke, dass beide verwirrend sind und nicht verwendet werden sollten.

Ich finde das hässlich und würde es eigentlich nicht im Code machen. Aber wenn Sie darauf bestehen, Ihre Loops auf diese Weise zu implementieren, würde ich das so machen.

Ich neige dazu zu glauben, dass Listenverständnisse und deren Art einen Versuch signalisieren sollten, etwas zu verwenden, das zumindest schwach einem funktionalen Stil ähnelt. Wenn Sie Dinge mit Nebenwirkungen setzen, die diese Annahme brechen, müssen die Leute Ihren Code genauer lesen, und ich denke, das ist eine schlechte Sache.

Allgegenwärtig
quelle
Was ist, wenn fun_with_side_effectsTrue zurückgegeben wird?
Katriel
7
Ich denke, diese Heilung ist schlimmer als die Krankheit - itertools.consume ist viel sauberer.
PaulMcG
@PaulMcG - itertools.consumeexistiert nicht mehr, wahrscheinlich weil es hässlich ist, Verständnis mit Nebenwirkungen zu verwenden.
Omnifarious
1
Es stellte sich heraus, dass ich mich geirrt hatte, und es gab es nie als Methode in der stdlib. Es ist ein Rezept in den itertools-Dokumenten: docs.python.org/3/library/…
PaulMcG