Listen-Abstraktion (List Comprehension)

Einführung

alternative to map, filter, reduce and lambda

Wir haben im vorherigen Kapitel "Lambda-Operator, Filter, Reduce und Map" erfahren, dass Guido van Rossum Listenverständnisse Konstrukten mit Map, Filtern, Reduzieren vorzieht und Lambda. In diesem Kapitel werden wir die wesentlichen Aspekte des Listenverständnisses behandeln.

Listenverständnisse wurden mit Python 2.0 hinzugefügt. Im Wesentlichen ist es Pythons Art, eine bekannte Notation für Mengen zu implementieren, wie sie von Mathematikern verwendet wird. In der Mathematik werden die quadratischen Zahlen der natürlichen Zahlen beispielsweise durch {x2 | erzeugt x ∈ ∈} oder die Menge komplexer Ganzzahlen {(x, y) | x ∈ ∈ ∧ y ∈ ∈}.

Das Listenverständnis ist eine elegante Möglichkeit, Listen in Python zu definieren und zu erstellen. Diese Listen haben oft die Eigenschaften von Mengen, sind aber nicht unbedingt Mengen.

Das Listenverständnis ist ein vollständiger Ersatz für die Lambda-Funktion sowie die Funktionen map (), filter () und redu (). Für die meisten Menschen ist die Syntax des Listenverständnisses leichter zu verstehen.

Beispiele

Im Kapitel über Lambda und map () haben wir eine map () -Funktion entworfen, um Celsius-Werte in Fahrenheit umzuwandeln und umgekehrt. Es sieht so aus mit Listenverständnis:

Celsius = [39.2, 36.5, 37.3, 37.8]
Fahrenheit = [ ((float(9)/5)*x + 32) for x in Celsius ]
print(Fahrenheit)
[102.56, 97.7, 99.14, 100.03999999999999]

Ein pythagoreisches Tripel besteht aus drei positiven ganzen Zahlen a, b und c, so dass $a^2 + b^2 = c^2$. Ein solches Tripel wird üblicherweise geschrieben (a, b, c), und das bekannteste Beispiel ist (3, 4, 5). Das folgende Listenverständnis erzeugt die pythagoreischen Tripel:

[(x,y,z) for x in range(1,30) for y in range(x,30) for z in range(y,30) if x**2 + y**2 == z**2]
Ausgabe: :

[(3, 4, 5),
 (5, 12, 13),
 (6, 8, 10),
 (7, 24, 25),
 (8, 15, 17),
 (9, 12, 15),
 (10, 24, 26),
 (12, 16, 20),
 (15, 20, 25),
 (20, 21, 29)]

Ein weiteres Beispiel: A und B seien zwei Mengen, das Kreuzprodukt (oder kartesische Produkt) von A und B, geschrieben A × B, ist die Menge aller Paare, wobei das erste Element ein Mitglied der Menge A und des zweiten Elements ist ist ein Mitglied der Menge B.

Mathematische Definition: A × B = {(a, b): a gehört zu A, b gehört zu B}. In Python ist das ganz einfach:

farben = [ "rot", "grün", "gelb", "blau" ]
sachen = [ "Haus", "Auto", "Baum" ]
farbige_sachen = [ (x,y) for x in farben for y in sachen ]
print(farbige_sachen)
[('rot', 'Haus'), ('rot', 'Auto'), ('rot', 'Baum'), ('grün', 'Haus'), ('grün', 'Auto'), ('grün', 'Baum'), ('gelb', 'Haus'), ('gelb', 'Auto'), ('gelb', 'Baum'), ('blau', 'Haus'), ('blau', 'Auto'), ('blau', 'Baum')]

Generatorverständnis

Generator-Verständnis wurde mit Python 2.6 eingeführt. Sie sind einfach wie ein Listenverständnis, aber mit Klammern - runden Klammern - anstelle von (eckigen) Klammern. Ansonsten ist die Syntax und die Arbeitsweise wie das Listenverständnis, aber ein Generatorverständnis gibt einen Generator anstelle einer Liste zurück.

x = (x **2 for x in range(20))
print(x)
x = list(x)
print(x)
<generator object <genexpr> at 0x000002441374D1C8>
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]

Ein anspruchsvolleres Beispiel

Berechnung der Primzahlen zwischen 1 und 100 mit dem Eratosthenes-Sieb:

kein_prim_zahl = [j for i in range(2, 8) for j in range(i*2, 100, i)]
prim_zahlen = [x for x in range(2, 100) if x not in kein_prim_zahl]
print(prim_zahlen)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

Wir wollen das vorherige Beispiel in eine allgemeinere Form bringen, damit wir die Liste der Primzahlen bis zu einer beliebigen Zahl n berechnen können:

from math import sqrt
n = 100
sqrt_n = int(sqrt(n))
kein_prim_zahl = [j for i in range(2, sqrt_n+1) for j in range(i*2, n, i)]
kein_prim_zahl
Ausgabe: :

[4,
 6,
 8,
 10,
 12,
 14,
 16,
 18,
 20,
 22,
 24,
 26,
 28,
 30,
 32,
 34,
 36,
 38,
 40,
 42,
 44,
 46,
 48,
 50,
 52,
 54,
 56,
 58,
 60,
 62,
 64,
 66,
 68,
 70,
 72,
 74,
 76,
 78,
 80,
 82,
 84,
 86,
 88,
 90,
 92,
 94,
 96,
 98,
 6,
 9,
 12,
 15,
 18,
 21,
 24,
 27,
 30,
 33,
 36,
 39,
 42,
 45,
 48,
 51,
 54,
 57,
 60,
 63,
 66,
 69,
 72,
 75,
 78,
 81,
 84,
 87,
 90,
 93,
 96,
 99,
 8,
 12,
 16,
 20,
 24,
 28,
 32,
 36,
 40,
 44,
 48,
 52,
 56,
 60,
 64,
 68,
 72,
 76,
 80,
 84,
 88,
 92,
 96,
 10,
 15,
 20,
 25,
 30,
 35,
 40,
 45,
 50,
 55,
 60,
 65,
 70,
 75,
 80,
 85,
 90,
 95,
 12,
 18,
 24,
 30,
 36,
 42,
 48,
 54,
 60,
 66,
 72,
 78,
 84,
 90,
 96,
 14,
 21,
 28,
 35,
 42,
 49,
 56,
 63,
 70,
 77,
 84,
 91,
 98,
 16,
 24,
 32,
 40,
 48,
 56,
 64,
 72,
 80,
 88,
 96,
 18,
 27,
 36,
 45,
 54,
 63,
 72,
 81,
 90,
 99,
 20,
 30,
 40,
 50,
 60,
 70,
 80,
 90]

Wenn wir uns den Inhalt von kein_prim_zahl ansehen, können wir sehen, dass wir ein Problem haben. Diese Liste enthält viele doppelte Einträge:

 
Ausgabe: :

[4,
 6,
 8,
 10,
 12,
 14,
 16,
 18,
 20,
 22,
 24,
 26,
 28,
 30,
 32,
 34,
 36,
 38,
 40,
 42,
 44,
 46,
 48,
 50,
 52,
 54,
 56,
 58,
 60,
 62,
 64,
 66,
 68,
 70,
 72,
 74,
 76,
 78,
 80,
 82,
 84,
 86,
 88,
 90,
 92,
 94,
 96,
 98,
 6,
 9,
 12,
 15,
 18,
 21,
 24,
 27,
 30,
 33,
 36,
 39,
 42,
 45,
 48,
 51,
 54,
 57,
 60,
 63,
 66,
 69,
 72,
 75,
 78,
 81,
 84,
 87,
 90,
 93,
 96,
 99,
 8,
 12,
 16,
 20,
 24,
 28,
 32,
 36,
 40,
 44,
 48,
 52,
 56,
 60,
 64,
 68,
 72,
 76,
 80,
 84,
 88,
 92,
 96,
 10,
 15,
 20,
 25,
 30,
 35,
 40,
 45,
 50,
 55,
 60,
 65,
 70,
 75,
 80,
 85,
 90,
 95,
 12,
 18,
 24,
 30,
 36,
 42,
 48,
 54,
 60,
 66,
 72,
 78,
 84,
 90,
 96,
 14,
 21,
 28,
 35,
 42,
 49,
 56,
 63,
 70,
 77,
 84,
 91,
 98,
 16,
 24,
 32,
 40,
 48,
 56,
 64,
 72,
 80,
 88,
 96,
 18,
 27,
 36,
 45,
 54,
 63,
 72,
 81,
 90,
 99,
 20,
 30,
 40,
 50,
 60,
 70,
 80,
 90]

Die Lösung für dieses unerträgliche Problem ist einfacher als Sie vielleicht denken. Es geht nur darum, eckige Klammern in geschweifte Klammern umzuwandeln, oder mit anderen Worten: Wir werden das Mengenverständnis verwenden.

Verständnis festlegen

Ein Mengenverständnis ähnelt einem Listenverständnis, gibt jedoch eine Menge und keine Liste zurück. Syntaktisch verwenden wir geschweifte Klammern anstelle von eckigen Klammern, um eine Menge zu erstellen. Das Verständnis von Sätzen ist die richtige Funktionalität, um unser Problem aus dem vorherigen Unterabschnitt zu lösen. Wir sind in der Lage, die Menge der Nicht-Primzahlen ohne Dubletten zu erstellen:

from math import sqrt
n = 100
sqrt_n = int(sqrt(n))
kein_prim_zahl = {j for i in range(2, sqrt_n+1) for j in range(i*2, n, i)}
kein_prim_zahl
Ausgabe: :

{4,
 6,
 8,
 9,
 10,
 12,
 14,
 15,
 16,
 18,
 20,
 21,
 22,
 24,
 25,
 26,
 27,
 28,
 30,
 32,
 33,
 34,
 35,
 36,
 38,
 39,
 40,
 42,
 44,
 45,
 46,
 48,
 49,
 50,
 51,
 52,
 54,
 55,
 56,
 57,
 58,
 60,
 62,
 63,
 64,
 65,
 66,
 68,
 69,
 70,
 72,
 74,
 75,
 76,
 77,
 78,
 80,
 81,
 82,
 84,
 85,
 86,
 87,
 88,
 90,
 91,
 92,
 93,
 94,
 95,
 96,
 98,
 99}
prim_zahlen= {i for i in range(2, n) if i not in kein_prim_zahl}
print(prim_zahlen)
{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97}

Rekursive Funktion zur Berechnung der Primzahlen

Das folgende Python-Skript verwendet eine rekursive Funktion, um die Primzahlen zu berechnen. Es beinhaltet die Tatsache, dass es ausreicht, die Vielfachen der Primzahlen bis zur Quadratwurzel von n zu untersuchen:

from math import sqrt
def prim_zahlen(n):
    if n == 0:
        return []
    elif n == 1:
        return []
    else:
        p = prim_zahlen(int(sqrt(n)))
        no_p = {j for i in p for j in range(i*2, n+1, i)}
        p = {x for x in range(2, n + 1) if x not in no_p}
    return p
for i in range(1,50):
    print(i, prim_zahlen(i))
1 []
2 {2}
3 {2, 3}
4 {2, 3}
5 {2, 3, 5}
6 {2, 3, 5}
7 {2, 3, 5, 7}
8 {2, 3, 5, 7}
9 {2, 3, 5, 7}
10 {2, 3, 5, 7}
11 {2, 3, 5, 7, 11}
12 {2, 3, 5, 7, 11}
13 {2, 3, 5, 7, 11, 13}
14 {2, 3, 5, 7, 11, 13}
15 {2, 3, 5, 7, 11, 13}
16 {2, 3, 5, 7, 11, 13}
17 {2, 3, 5, 7, 11, 13, 17}
18 {2, 3, 5, 7, 11, 13, 17}
19 {2, 3, 5, 7, 11, 13, 17, 19}
20 {2, 3, 5, 7, 11, 13, 17, 19}
21 {2, 3, 5, 7, 11, 13, 17, 19}
22 {2, 3, 5, 7, 11, 13, 17, 19}
23 {2, 3, 5, 7, 11, 13, 17, 19, 23}
24 {2, 3, 5, 7, 11, 13, 17, 19, 23}
25 {2, 3, 5, 7, 11, 13, 17, 19, 23}
26 {2, 3, 5, 7, 11, 13, 17, 19, 23}
27 {2, 3, 5, 7, 11, 13, 17, 19, 23}
28 {2, 3, 5, 7, 11, 13, 17, 19, 23}
29 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
30 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
31 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}
32 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}
33 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}
34 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}
35 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}
36 {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31}
37 {2, 3, 5, 37, 7, 11, 13, 17, 19, 23, 29, 31}
38 {2, 3, 5, 37, 7, 11, 13, 17, 19, 23, 29, 31}
39 {2, 3, 5, 37, 7, 11, 13, 17, 19, 23, 29, 31}
40 {2, 3, 5, 37, 7, 11, 13, 17, 19, 23, 29, 31}
41 {2, 3, 5, 37, 7, 41, 11, 13, 17, 19, 23, 29, 31}
42 {2, 3, 5, 37, 7, 41, 11, 13, 17, 19, 23, 29, 31}
43 {2, 3, 5, 37, 7, 41, 11, 43, 13, 17, 19, 23, 29, 31}
44 {2, 3, 5, 37, 7, 41, 11, 43, 13, 17, 19, 23, 29, 31}
45 {2, 3, 5, 37, 7, 41, 11, 43, 13, 17, 19, 23, 29, 31}
46 {2, 3, 5, 37, 7, 41, 11, 43, 13, 17, 19, 23, 29, 31}
47 {2, 3, 5, 37, 7, 41, 11, 43, 13, 47, 17, 19, 23, 29, 31}
48 {2, 3, 5, 37, 7, 41, 11, 43, 13, 47, 17, 19, 23, 29, 31}
49 {2, 3, 5, 37, 7, 41, 11, 43, 13, 47, 17, 19, 23, 29, 31}

Unterschiede zwischen Version 2.x und 3.x.

In Python 2 ist die Schleifensteuervariable nicht lokal, d. H. Sie kann eine andere Variable dieses Namens außerhalb des Listenverständnisses ändern, wie wir im folgenden Beispiel sehen können:

x = "This value will be changed in the list comprehension"
res = [x for x in range(3)]
res
[0, 1, 2]
x
2
res = [i for i in range(5)]
i
4

Guido van Rossum bezeichnete diesen Effekt als "eines von Pythons 'schmutzigen kleinen Geheimnissen' seit Jahren" .1 Der Grund dafür war die Effizienz. "Es begann als absichtlicher Kompromiss, Listenverständnisse unglaublich schnell zu machen, und obwohl es für Anfänger keine häufige Gefahr war, stach es definitiv gelegentlich Menschen."

Dieses "schmutzige kleine Geheimnis" ist in Python3 behoben, wie Sie im folgenden Code sehen können:

x = "Python 3 fixed the dirty little secret"
res = [x for x in range(3)]
print(res)
[0, 1, 2]
x 
[0, 1, 2]
Ausgabe: :

'Python 3 fixed the dirty little secret'

Fußnoten: 1 Guido van Rossum: From List Comprehensions to Generator Expressions

2 dto.