Betrachten Sie das folgende Spielzeugbeispiel:

>>> def square(x): return x*x
... 
>>> [square(x) for x in range(12) if square(x) > 50]
[64, 81, 100, 121]

Ich muss im Listenverständnis zweimal Quadrat (x) aufrufen. Die Duplizierung ist hässlich, fehleranfällig (es ist einfach, nur einen der beiden Aufrufe zu ändern, wenn der Code geändert wird) und ineffizient.

Natürlich kann ich das machen:

>>> squares = [square(x) for x in range(12)]
>>> [s for s in squares if s > 50]
[64, 81, 100, 121]

Oder dieses:

[s for s in [square(x) for x in range(12)] if s > 50]

Beide sind lebenswert, aber es scheint, als gäbe es eine Möglichkeit, alles in einer einzigen Aussage zu erledigen, ohne die beiden Listenverständnisse zu verschachteln. Ich weiß, dass ich sie beim nächsten Lesen des Artikels eine Weile anstarren muss Code nur um herauszufinden, was los ist. Gibt es einen Weg?

Ich denke, eine faire Frage an mich wäre, wie ich mir eine solche Syntax vorstellen könnte. Hier sind zwei Ideen, die sich in Python jedoch weder idiomatisch anfühlen (noch funktionieren). Sie sind inspiriert von anaphorischen Makros in Lisp.

[square(x) for x in range(12) if it > 50]
[it=square(x) for x in range(12) if it > 50]
12
kuzzooroo 20 Nov. 2013 im 04:35

5 Antworten

Beste Antwort

Sie sollten einen Generator verwenden:

[s for s in (square(x) for x in range(12)) if s > 50]

Dadurch wird vermieden, dass eine ungefilterte Zwischenliste von Quadraten erstellt wird.

20
arshajii 20 Nov. 2013 im 00:38

Hier ist ein Vergleich von verschachtelten Generator- und "verketteten" Listenkompositionen mit zweimaliger Berechnung

$ python -m timeit "[s for n in range(12) for s in [n * n] if s > 50]"
100000 loops, best of 3: 2.48 usec per loop
$ python -m timeit "[s for s in (x * x for x in range(12)) if s > 50]"
1000000 loops, best of 3: 1.89 usec per loop
$ python -m timeit "[n * n for n in range(12) if n * n > 50]"
1000000 loops, best of 3: 1.1 usec per loop

$ pypy -m timeit "[s for n in range(12) for s in [n * n] if s > 50]"
1000000 loops, best of 3: 0.211 usec per loop
$ pypy -m timeit "[s for s in (x * x for x in range(12)) if s > 50]"
1000000 loops, best of 3: 0.359 usec per loop
$ pypy -m timeit "[n * n for n in range(12) if n * n > 50]"
10000000 loops, best of 3: 0.0834 usec per loop

Ich habe n * n anstelle von square(n) verwendet, weil es praktisch war und den Funktionsaufruf-Overhead von der Benckmark entfernt

TLDR: In einfachen Fällen ist es möglicherweise am besten, die Berechnung einfach zu duplizieren.

9
John La Rooy 20 Nov. 2013 im 01:36

BEARBEITEN: Ich bin blind und dupliziert Eevees Antwort.

Es ist möglich, die Iteration über eine Liste mit 1 Elementen zu missbrauchen, um Zwischenvariablen zu "binden":

[s for x in range(12) for s in [square(x)] if s > 50]

Ich zögere jedoch, dies als lesbare Lösung zu empfehlen.

Pro: Im Vergleich zum verschachtelten Verständnis bevorzuge ich die Reihenfolge hier - mit for x in range(12) außerhalb. Sie können es einfach nacheinander lesen, anstatt hinein- und wieder herauszuzoomen ...

Con: Das for s in [...] ist ein nicht idiomatischer Hack und kann den Lesern eine Pause geben. Das verschachtelte Verständnis ist zwar wohl schwerer zu entschlüsseln, verwendet jedoch Sprachmerkmale auf "offensichtliche" Weise.

  • Idee: Umbenennen der Zwischenvariablen wie tmp könnte ich es klarer machen.

Das Fazit ist, dass ich auch nicht zufrieden bin. Am wahrscheinlichsten ist es, den Zwischengenerator zu benennen:

squares = (square(x) for x in range(12))
result = [s for s in squares if s > 50]

[Randnotiz: Das Benennen von Ergebnissen von Generatorausdrücken ist etwas selten. Aber lesen Sie David Beazleys Vortrag und es könnte an Ihnen wachsen.]

OTOH Wenn Sie solche Konstrukte häufig schreiben, wählen Sie das for tmp in [expr(x)] - Muster - es wird in Ihrem Code "lokal idiomatisch" und sobald Sie es kennen, zahlt sich seine Kompaktheit aus. Mein Anliegen in Bezug auf die Lesbarkeit ist mehr die einmalige Verwendung ...

1
3 revs 23 Mai 2017 im 12:30

Eine andere Alternative, bei der "verkettete" Listenkompositionen anstelle von verschachtelten verwendet werden:

[s for n in range(12) for s in [square(n)] if s > 50]

Könnte aber eine seltsame Lektüre sein.

7
Eevee 20 Nov. 2013 im 00:43
[square(s) for s in range(12) if s >= 7]  # sqrt(50) = 7.071...

Oder noch einfacher (keine Verzweigung, woo!)

[square(s) for s in range(7, 12)]  # sqrt(50) = 7.071...
3
inspectorG4dget 20 Nov. 2013 im 00:40