Python Language
Listenverständnisse
Suche…
Einführung
[i ** 2 for i in range(1,11)]
Die Dummy - i
aus einer bestehenden Liste range
ist ein neues Element Muster machen verwendet. Es wird verwendet, wenn eine for-Schleife in weniger ausdrucksstarken Sprachen erforderlich ist.
Syntax
- [i für i in range (10)] # grundlegendes Listenverständnis
- [i for i in xrange (10)] # grundlegendes Listenverständnis mit Generatorobjekt in Python 2.x
- [i für i im Bereich (20), wenn i% 2 == 0] # mit Filter
- [x + y für x in [1, 2, 3] für y in [3, 4, 5]] # verschachtelten Schleifen
- [i wenn i> 6, sonst 0 für i im Bereich (10)] # ternärer Ausdruck
- [i wenn i> 4 sonst 0 für i im Bereich (20), wenn i% 2 == 0] # mit Filter und ternärem Ausdruck
- [[x + y für x in [1, 2, 3]] für y in [3, 4, 5]] # geschachteltes Listenverständnis
Bemerkungen
Listenverständnisse wurden in PEP 202 beschrieben und in Python 2.0 eingeführt.
Bedingte Listenverständnisse
Bei einem Listenverständnis können Sie eine oder mehrere if
Bedingungen anhängen, um Werte zu filtern.
[<expression> for <element> in <iterable> if <condition>]
Für jedes <element>
in <iterable>
; Wenn <condition>
zu True
ausgewertet wird, fügen Sie der zurückgegebenen Liste <expression>
(normalerweise eine Funktion von <element>
) hinzu.
Dies kann zum Beispiel verwendet werden, um nur gerade Zahlen aus einer Folge von ganzen Zahlen zu extrahieren:
[x for x in range(10) if x % 2 == 0]
# Out: [0, 2, 4, 6, 8]
Der obige Code entspricht:
even_numbers = []
for x in range(10):
if x % 2 == 0:
even_numbers.append(x)
print(even_numbers)
# Out: [0, 2, 4, 6, 8]
Auch ein bedingtes Listenverständnis der Form [e for x in y if c]
(wobei e
und c
Ausdrücke in Form von x
) ist gleichbedeutend mit list(filter(lambda x: c, map(lambda x: e, y)))
.
Beachten Sie trotz des gleichen Ergebnisses, dass das erste Beispiel fast doppelt so schnell ist wie das zweite. Für Neugierige ist dies eine nette Erklärung für den Grund.
Beachten Sie, dass dies ganz anders ist als der Bedingungsausdruck ... if ... else ...
(manchmal auch als ternärer Ausdruck bezeichnet ), den Sie für den Teil <expression>
des Listenverständnisses verwenden können. Betrachten Sie das folgende Beispiel:
[x if x % 2 == 0 else None for x in range(10)]
# Out: [0, None, 2, None, 4, None, 6, None, 8, None]
Hier ist der Bedingungsausdruck kein Filter, sondern ein Operator, der den für die Listenelemente zu verwendenden Wert bestimmt:
<value-if-condition-is-true> if <condition> else <value-if-condition-is-false>
Dies wird deutlicher, wenn Sie es mit anderen Operatoren kombinieren:
[2 * (x if x % 2 == 0 else -1) + 1 for x in range(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Wenn Sie Python 2.7 verwenden, ist xrange
aus verschiedenen Gründen möglicherweise besser als der range
wie in der xrange
Dokumentation beschrieben .
[2 * (x if x % 2 == 0 else -1) + 1 for x in xrange(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Der obige Code entspricht:
numbers = []
for x in range(10):
if x % 2 == 0:
temp = x
else:
temp = -1
numbers.append(2 * temp + 1)
print(numbers)
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]
Man kann ternäre Ausdrücke und if
Bedingungen kombinieren. Der ternäre Operator arbeitet mit dem gefilterten Ergebnis:
[x if x > 2 else '*' for x in range(10) if x % 2 == 0]
# Out: ['*', '*', 4, 6, 8]
Dasselbe konnte nicht nur von einem ternären Operator erreicht werden:
[x if (x > 2 and x % 2 == 0) else '*' for x in range(10)]
# Out:['*', '*', '*', '*', 4, '*', 6, '*', 8, '*']
Siehe auch: Filter , die häufig eine ausreichende Alternative zu bedingten Listenverständnissen darstellen.
Listenverständnisse mit verschachtelten Schleifen
Listenverständnisse können verschachtelt for
Schleifen verwendet werden. Sie können eine beliebige Anzahl von verschachtelten for-Schleifen innerhalb eines Listenverständnisses codieren. Jeder for
Schleife kann ein optionaler if
Test zugeordnet werden. Dabei ist die Reihenfolge der for
ist Konstrukte die gleiche Reihenfolge wie bei einer Reihe von verschachtelten Schreiben for
Anweisungen. Die allgemeine Struktur von Listenverstehen sieht folgendermaßen aus:
[ expression for target1 in iterable1 [if condition1]
for target2 in iterable2 [if condition2]...
for targetN in iterableN [if conditionN] ]
Der folgende Code reduziert beispielsweise eine Liste mit mehreren for
Anweisungen:
data = [[1, 2], [3, 4], [5, 6]]
output = []
for each_list in data:
for element in each_list:
output.append(element)
print(output)
# Out: [1, 2, 3, 4, 5, 6]
kann als Listenverständnis mit mehreren for
Konstrukte äquivalent geschrieben werden:
data = [[1, 2], [3, 4], [5, 6]]
output = [element for each_list in data for element in each_list]
print(output)
# Out: [1, 2, 3, 4, 5, 6]
Sowohl in der erweiterten Form als auch im Listenverständnis steht die äußere Schleife (erste Anweisung) an erster Stelle.
Das verschachtelte Verständnis ist nicht nur kompakter, sondern auch wesentlich schneller.
In [1]: data = [[1,2],[3,4],[5,6]]
In [2]: def f():
...: output=[]
...: for each_list in data:
...: for element in each_list:
...: output.append(element)
...: return output
In [3]: timeit f()
1000000 loops, best of 3: 1.37 µs per loop
In [4]: timeit [inner for outer in data for inner in outer]
1000000 loops, best of 3: 632 ns per loop
Der Aufwand für den Funktionsaufruf oben beträgt ungefähr 140ns .
Inline, if
s ähnlich verschachtelt sind und an einer beliebigen Stelle nach der ersten for
:
data = [[1], [2, 3], [4, 5]]
output = [element for each_list in data
if len(each_list) == 2
for element in each_list
if element != 5]
print(output)
# Out: [2, 3, 4]
Der besseren Lesbarkeit halber sollten Sie jedoch traditionelle For-Loops verwenden . Dies gilt insbesondere, wenn das Schachteln mehr als zwei Ebenen tief ist und / oder die Logik des Verständnisses zu komplex ist. Das Verständnis für mehrere verschachtelte Schleifenlisten kann fehleranfällig sein oder unerwartete Ergebnisse liefern.
Refactoring-Filter und Karte zum Auflisten von Verständnis
Die filter
oder map
sollten häufig durch Listenverständnisse ersetzt werden. Guido Van Rossum beschreibt dies gut in einem offenen Brief aus dem Jahr 2005 :
filter(P, S)
wird fast immer klarer geschrieben als[x for x in S if P(x)]
, und dies hat den großen Vorteil, dass die häufigsten Verwendungen Vergleichselemente beinhalten, z. B.x==42
und definierend Ein Lambda dafür erfordert nur viel mehr Aufwand für den Leser (plus das Lambda ist langsamer als das Listenverständnis). Dies gilt umso mehr für diemap(F, S)
die zu[F(x) for x in S]
. In vielen Fällen können Sie jedoch stattdessen Generatorausdrücke verwenden.
Die folgenden Codezeilen gelten als " nicht pythonisch " und verursachen Fehler in vielen Python-Linters.
filter(lambda x: x % 2 == 0, range(10)) # even numbers < 10
map(lambda x: 2*x, range(10)) # multiply each number by two
reduce(lambda x,y: x+y, range(10)) # sum of all elements in list
Nach dem, was wir aus dem vorherigen Zitat gelernt haben, können wir diese filter
und map
in ihre entsprechenden Listenverständnisse unterteilen . Entfernen Sie auch die Lambda- Funktionen von jedem - dadurch wird der Code lesbarer.
# Filter:
# P(x) = x % 2 == 0
# S = range(10)
[x for x in range(10) if x % 2 == 0]
# Map
# F(x) = 2*x
# S = range(10)
[2*x for x in range(10)]
Die Lesbarkeit wird noch deutlicher bei Verkettungsfunktionen. Wo aus Gründen der Lesbarkeit die Ergebnisse einer Map- oder Filterfunktion an die nächste weitergegeben werden sollten; In einfachen Fällen können diese durch ein einzelnes Listenverständnis ersetzt werden. Darüber hinaus können wir anhand des Listenverständnisses leicht erkennen, was das Ergebnis unseres Prozesses ist, wo die kognitive Belastung höher ist, wenn über den verketteten Map & Filter-Prozess vorgegangen wird.
# Map & Filter
filtered = filter(lambda x: x % 2 == 0, range(10))
results = map(lambda x: 2*x, filtered)
# List comprehension
results = [2*x for x in range(10) if x % 2 == 0]
Refactoring - Kurzanleitung
Karte
map(F, S) == [F(x) for x in S]
Filter
filter(P, S) == [x for x in S if P(x)]
Dabei sind F
und P
Funktionen, die jeweils Eingangswerte transformieren und einen bool
Verschachtelte Listenverständnisse
Geschachtelte Listenverständnisse sind im Gegensatz zu Listenverständnissen mit verschachtelten Schleifen Listenverständnisse im Listenverständnis. Der Anfangsausdruck kann ein beliebiger Ausdruck sein, einschließlich eines anderen Listenverständnisses.
#List Comprehension with nested loop
[x + y for x in [1, 2, 3] for y in [3, 4, 5]]
#Out: [4, 5, 6, 5, 6, 7, 6, 7, 8]
#Nested List Comprehension
[[x + y for x in [1, 2, 3]] for y in [3, 4, 5]]
#Out: [[4, 5, 6], [5, 6, 7], [6, 7, 8]]
Das verschachtelte Beispiel ist äquivalent zu
l = []
for y in [3, 4, 5]:
temp = []
for x in [1, 2, 3]:
temp.append(x + y)
l.append(temp)
Ein Beispiel, bei dem ein verschachteltes Verständnis verwendet werden kann, um eine Matrix zu transponieren.
matrix = [[1,2,3],
[4,5,6],
[7,8,9]]
[[row[i] for row in matrix] for i in range(len(matrix))]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Wie bei verschachtelten for
Schleifen gibt es keine Grenzen dafür, wie tiefes Verständnis verschachtelt werden kann.
[[[i + j + k for k in 'cd'] for j in 'ab'] for i in '12']
# Out: [[['1ac', '1ad'], ['1bc', '1bd']], [['2ac', '2ad'], ['2bc', '2bd']]]
Iteriere zwei oder mehr Listen gleichzeitig innerhalb des Listenverständnisses
Um innerhalb des Listenverständnisses mehr als zwei Listen gleichzeitig zu durchlaufen , kann zip()
werden:
>>> list_1 = [1, 2, 3 , 4]
>>> list_2 = ['a', 'b', 'c', 'd']
>>> list_3 = ['6', '7', '8', '9']
# Two lists
>>> [(i, j) for i, j in zip(list_1, list_2)]
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
# Three lists
>>> [(i, j, k) for i, j, k in zip(list_1, list_2, list_3)]
[(1, 'a', '6'), (2, 'b', '7'), (3, 'c', '8'), (4, 'd', '9')]
# so on ...