Python Language
Listen Sie Verständnis auf
Suche…
Einführung
Listenverständnisse in Python sind prägnante, syntaktische Konstrukte. Sie können verwendet werden, um Listen aus anderen Listen zu generieren, indem auf jedes Element in der Liste Funktionen angewendet werden. Im folgenden Abschnitt wird die Verwendung dieser Ausdrücke erläutert und veranschaulicht.
Syntax
- [x + 1 für x in (1, 2, 3)] # Listenverständnis, ergibt [2, 3, 4]
- (x + 1 für x in (1, 2, 3)) # Generatorausdruck ergibt 2, dann 3, dann 4
- [x für x in (1, 2, 3), wenn x% 2 == 0] # Listenverständnis mit Filter, ergibt [2]
- [x + 1 wenn x% 2 == 0, sonst x für x in (1, 2, 3)] # Listenverständnis mit ternär
- [x + 1, wenn x% 2 == 0, sonst x für x im Bereich (-3,4), wenn x> 0] # Listenverständnis mit ternärer und Filterung
- {x für x in (1, 2, 2, 3)} # Setverstehen, ergibt {1, 2, 3}
- {k: v für k, v in [('a', 1), ('b', 2)]} # dict Verständnis, ergibt {'a': 1, 'b': 2} (Python 2.7+ und 3.0+ nur)
- [x + y für x in [1, 2] für y in [10, 20]] # Verschachtelte Schleifen, ergibt [11, 21, 12, 22]
- [x + y für x in [1, 2, 3], wenn x> 2 für y in [3, 4, 5]] # Bedingung am 1. für Schleife geprüft
- [x + y für x in [1, 2, 3] für y in [3, 4, 5], wenn x> 2] # Bedingung am 2. für Schleife geprüft
- [x für x in xrange (10), wenn x% 2 == 0] # Bedingung geprüft, wenn geschleifte Zahlen ungerade Zahlen sind
Bemerkungen
Verständnis sind syntaktische Konstrukte, die Datenstrukturen oder Ausdrücke definieren, die für eine bestimmte Sprache eindeutig sind. Durch die korrekte Verwendung von Verständnis werden diese in leicht verständliche Ausdrücke neu interpretiert. Als Ausdrücke können sie verwendet werden:
- auf der rechten Seite der Aufgaben
- als Argumente für Funktionsaufrufe
- im Körper einer Lambda-Funktion
- als eigenständige Aussagen. (Zum Beispiel:
[print(x) for x in range(10)]
)
Listenverständnisse
Ein Listenverständnis erstellt eine neue list
indem auf jedes Element eines iterierbaren Elements ein Ausdruck angewendet wird . Die grundlegendste Form ist:
[ <expression> for <element> in <iterable> ]
Es gibt auch eine optionale 'if'-Bedingung:
[ <expression> for <element> in <iterable> if <condition> ]
Jedes <element>
in <iterable>
wird an <expression>
wenn die (optionale) <condition>
als wahr ausgewertet wird . Alle Ergebnisse werden sofort in der neuen Liste zurückgegeben. Generatorausdrücke werden träge ausgewertet, Listenauffassungen bewerten jedoch den gesamten Iterator sofort, wobei der Speicher proportional zur Länge des Iterators ist.
So erstellen Sie eine list
von quadrierten Ganzzahlen:
squares = [x * x for x in (1, 2, 3, 4)]
# squares: [1, 4, 9, 16]
Der for
Ausdruck setzt x
für jeden Wert aus (1, 2, 3, 4)
. Das Ergebnis des Ausdrucks x * x
wird an eine interne list
angehängt. Die interne list
wird den variablen squares
zugewiesen, wenn sie abgeschlossen sind.
Abgesehen von einer Geschwindigkeitssteigerung (wie hier erklärt) entspricht ein Listenverständnis ungefähr der folgenden for-Schleife:
squares = []
for x in (1, 2, 3, 4):
squares.append(x * x)
# squares: [1, 4, 9, 16]
Der auf jedes Element angewendete Ausdruck kann so komplex sein wie nötig:
# Get a list of uppercase characters from a string
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']
# Strip off any commas from the end of strings in a list
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']
# Organize letters in words more reasonably - in an alphabetical order
sentence = "Beautiful is better than ugly"
["".join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']
sonst
else
kann in List-Verständniskonstrukten verwendet werden. Seien Sie jedoch vorsichtig bei der Syntax. Die if / else-Klauseln sollten vor der for
Schleife verwendet werden, nicht nach:
# create a list of characters in apple, replacing non vowels with '*'
# Ex - 'apple' --> ['a', '*', '*', '*' ,'e']
[x for x in 'apple' if x in 'aeiou' else '*']
#SyntaxError: invalid syntax
# When using if/else together use them before the loop
[x if x in 'aeiou' else '*' for x in 'apple']
#['a', '*', '*', '*', 'e']
Beachten Sie, dass dies ein anderes Sprachkonstrukt verwendet, einen bedingten Ausdruck , der selbst nicht zur Verständnissyntax gehört . Das if
after- for…in
ist ein Teil von List-Verständnis und dient zum Filtern von Elementen aus der Quelle.
Doppelte Iteration
Die Reihenfolge der doppelten Iteration [... for x in ... for y in ...]
ist entweder natürlich oder kontraintuitiv. Als Faustregel gilt ein Äquivalent for
Schleife:
def foo(i):
return i, i + 0.5
for i in range(3):
for x in foo(i):
yield str(x)
Dies wird zu:
[str(x)
for i in range(3)
for x in foo(i)
]
Dies kann als [str(x) for i in range(3) for x in foo(i)]
in eine Zeile komprimiert werden.
In-Place-Mutation und andere Nebenwirkungen
Verstehen Sie vor dem Verwenden des Listenverständnisses den Unterschied zwischen Funktionen, die für ihre Nebeneffekte ( Mutations- oder In-Place- Funktionen) aufgerufen werden, die normalerweise None
, und Funktionen, die einen interessanten Wert zurückgeben.
Viele Funktionen (vor allem reine Funktionen) nehmen einfach ein Objekt und geben ein Objekt zurück. Eine In-Place- Funktion ändert das vorhandene Objekt, was als Nebeneffekt bezeichnet wird . Andere Beispiele umfassen Eingabe- und Ausgabeoperationen wie Drucken.
list.sort()
sortiert eine Liste an Ort und Stelle (bedeutet, dass die ursprüngliche Liste list.sort()
) und gibt den Wert None
. Daher funktioniert es nicht wie erwartet in einem Listenverständnis:
[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]
Stattdessen sorted()
eine sortierte list
anstatt direkt zu sortieren:
[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
# [[1, 2], [3, 4], [0, 1]]
Die Verwendung von Nachteilen für Nebenwirkungen ist möglich, z. B. E / A- oder In-Place-Funktionen. Eine for-Schleife ist jedoch in der Regel besser lesbar. Während dies in Python 3 funktioniert:
[print(x) for x in (1, 2, 3)]
Verwenden Sie stattdessen:
for x in (1, 2, 3):
print(x)
In einigen Situationen sind Nebeneffekt Funktionen geeignet für Liste Verständnis. random.randrange()
hat den Nebeneffekt, den Zustand des Zufallszahlengenerators zu ändern, gibt aber auch einen interessanten Wert zurück. next()
kann außerdem auf einem Iterator aufgerufen werden.
Der folgende Zufallswertgenerator ist nicht rein, macht jedoch Sinn, da der Zufallsgenerator bei jeder Auswertung des Ausdrucks zurückgesetzt wird:
from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]
Whitespace in Listenverständnissen
Kompliziertere Listenverständnisse können eine unerwünschte Länge erreichen oder weniger lesbar werden. Obwohl es in Beispielen weniger üblich ist, ist es möglich, ein Listenverständnis wie folgt in mehrere Zeilen aufzuteilen:
[
x for x
in 'foo'
if x not in 'bar'
]
Wörterbuch Verständnis
Ein Wörterbuchverständnis ähnelt einem Listenverständnis, außer dass es ein Wörterbuchobjekt anstelle einer Liste erzeugt.
Ein grundlegendes Beispiel:
{x: x * x for x in (1, 2, 3, 4)}
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
Das ist nur eine andere Schreibweise:
dict((x, x * x) for x in (1, 2, 3, 4))
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
Wie bei einem Listenverständnis können wir eine bedingte Anweisung innerhalb des Diktierverstehens verwenden, um nur die Diktierelemente zu erzeugen, die ein bestimmtes Kriterium erfüllen.
{name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}
# Out: {'Exchange': 8, 'Overflow': 8}
Oder mit einem Generatorausdruck umgeschrieben.
dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# Out: {'Exchange': 8, 'Overflow': 8}
Beginnend mit einem Wörterbuch und Verwenden des Wörterbuchverständnisses als Schlüsselwertpaarfilter
initial_dict = {'x': 1, 'y': 2}
{key: value for key, value in initial_dict.items() if key == 'x'}
# Out: {'x': 1}
Schlüssel und Wert des Wörterbuchs umschalten (Wörterbuch umkehren)
Wenn Sie über ein Diktier verfügen, das einfache Hashwerte enthält (doppelte Werte können unerwartete Ergebnisse haben):
my_dict = {1: 'a', 2: 'b', 3: 'c'}
und Sie wollten die Schlüssel und Werte austauschen, Sie können je nach Codierstil verschiedene Methoden verwenden:
-
swapped = {v: k for k, v in my_dict.items()}
-
swapped = dict((v, k) for k, v in my_dict.iteritems())
-
swapped = dict(zip(my_dict.values(), my_dict))
-
swapped = dict(zip(my_dict.values(), my_dict.keys()))
-
swapped = dict(map(reversed, my_dict.items()))
print(swapped)
# Out: {a: 1, b: 2, c: 3}
Wenn Ihr Wörterbuch groß ist , sollten Sie itertools importieren und izip
oder imap
.
Wörterbücher zusammenführen
Kombinieren Sie Wörterbücher und überschreiben Sie gegebenenfalls alte Werte mit einem verschachtelten Wörterbuchverständnis.
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}
{k: v for d in [dict1, dict2] for k, v in d.items()}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Das Auspacken von Wörterbüchern ( PEP 448 ) kann jedoch bevorzugt sein.
{**dict1, **dict2}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Hinweis : Die Wörterbücher wurden in Python 3.0 hinzugefügt und im Gegensatz zu den Listenfunktionen, die in 2.0 hinzugefügt wurden, auf 2.7+ zurückportiert. Versionen <2.7 können Generatorausdrücke und das dict()
verwenden, um das Verhalten von Wörterbuchverstehen zu simulieren.
Generator-Ausdrücke
Generatorausdrücke sind Listenverständnissen sehr ähnlich. Der Hauptunterschied besteht darin, dass nicht alle Ergebnisse auf einmal erstellt werden. Es erstellt ein Generatorobjekt, das dann wiederholt werden kann.
Sehen Sie sich zum Beispiel den Unterschied im folgenden Code an:
# list comprehension
[x**2 for x in range(10)]
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# generator comprehension
(x**2 for x in xrange(10))
# Output: <generator object <genexpr> at 0x11b4b7c80>
Dies sind zwei sehr unterschiedliche Objekte:
Die folgende Liste gibt ein Verständnis
list
Objekt während der Generator ein Verständnis kehrtgenerator
.generator
können nicht indiziert werden und verwenden dienext
Funktion, um die Elemente in der richtigen Reihenfolge zu erhalten.
Hinweis : Wir verwenden xrange
da auch hier ein Generatorobjekt erstellt wird. Wenn wir den Bereich verwenden würden, würde eine Liste erstellt. xrange
ist auch nur in einer späteren Version von Python 2 vorhanden. In Python 3 gibt range
lediglich einen Generator zurück. Weitere Informationen finden Sie im Beispiel Unterschiede zwischen Range- und Xrange-Funktionen .
g = (x**2 for x in xrange(10))
print(g[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'
g.next() # 0
g.next() # 1
g.next() # 4
...
g.next() # 81
g.next() # Throws StopIteration Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
HINWEIS: Die Funktion
g.next()
sollte durchnext(g)
undxrange
mitrange
daIterator.next()
undxrange()
in Python 3 nicht vorhanden sind.
Obwohl beide auf ähnliche Weise durchlaufen werden können:
for i in [x**2 for x in range(10)]:
print(i)
"""
Out:
0
1
4
...
81
"""
for i in (x**2 for x in xrange(10)):
print(i)
"""
Out:
0
1
4
.
.
.
81
"""
Anwendungsfälle
Generatorausdrücke werden träge ausgewertet, was bedeutet, dass sie jeden Wert nur dann erzeugen und zurückgeben, wenn der Generator wiederholt wird. Dies ist häufig nützlich, wenn Sie große Datensätze durchlaufen, um zu vermeiden, dass ein Duplikat des Datensatzes im Arbeitsspeicher erstellt werden muss:
for square in (x**2 for x in range(1000000)):
#do something
Ein weiterer häufiger Anwendungsfall ist das Vermeiden der Iteration über eine gesamte Iterierbarkeit, wenn dies nicht erforderlich ist. In diesem Beispiel wird bei jeder get_objects()
von get_objects()
ein Element von einer Remote-API abgerufen. Es können Tausende von Objekten vorhanden sein, die einzeln abgerufen werden müssen, und wir müssen nur wissen, ob ein Objekt existiert, das mit einem Muster übereinstimmt. Durch Verwendung eines Generatorausdrucks, wenn ein Objekt auf das Muster passt.
def get_objects():
"""Gets objects from an API one by one"""
while True:
yield get_next_item()
def object_matches_pattern(obj):
# perform potentially complex calculation
return matches_pattern
def right_item_exists():
items = (object_matched_pattern(each) for each in get_objects())
for item in items:
if item.is_the_right_one:
return True
return False
Verstehen festlegen
Das Satzverständnis ähnelt dem Listen- und Wörterbuchverständnis , erzeugt jedoch einen Satz , bei dem es sich um eine ungeordnete Sammlung eindeutiger Elemente handelt.
# A set containing every value in range(5):
{x for x in range(5)}
# Out: {0, 1, 2, 3, 4}
# A set of even numbers between 1 and 10:
{x for x in range(1, 11) if x % 2 == 0}
# Out: {2, 4, 6, 8, 10}
# Unique alphabetic characters in a string of text:
text = "When in the Course of human events it becomes necessary for one people..."
{ch.lower() for ch in text if ch.isalpha()}
# Out: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
# 'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])
Denken Sie daran, dass Sets ungeordnet sind. Dies bedeutet, dass die Reihenfolge der Ergebnisse in der Gruppe von der in den obigen Beispielen dargestellten abweichen kann.
Hinweis : Das Satzverständnis ist ab Python 2.7+ verfügbar, im Gegensatz zu Listenverständnissen, die in 2.0 hinzugefügt wurden. In Python 2.2 bis Python 2.6 kann die Funktion set()
mit einem Generatorausdruck verwendet werden, um dasselbe Ergebnis zu erzeugen:
set(x for x in range(5))
# Out: {0, 1, 2, 3, 4}
Vermeiden Sie sich wiederholende und teure Operationen mit Bedingungsklausel
Betrachten Sie das folgende Listenverständnis:
>>> def f(x):
... import time
... time.sleep(.1) # Simulate expensive function
... return x**2
>>> [f(x) for x in range(1000) if f(x) > 10]
[16, 25, 36, ...]
Dies führt zu zwei Aufrufen von f(x)
für 1.000 Werte von x
: einen Aufruf zum Erzeugen des Werts und den anderen zum Überprüfen der if
Bedingung. Wenn f(x)
eine besonders teure Operation ist, kann dies erhebliche Auswirkungen auf die Leistung haben. Schlimmer noch, wenn der Aufruf von f()
Nebenwirkungen hat, kann dies zu überraschenden Ergebnissen führen.
Stattdessen sollten Sie die teure Operation nur einmal für jeden Wert von x
auswerten, indem Sie eine iterierbare Zwischenstufe ( Generatorausdruck ) wie folgt erstellen:
>>> [v for v in (f(x) for x in range(1000)) if v > 10]
[16, 25, 36, ...]
Oder mit der eingebauten Karte äquivalent:
>>> [v for v in map(f, range(1000)) if v > 10]
[16, 25, 36, ...]
Eine andere Möglichkeit, die zu einem besser lesbaren Code führen könnte, besteht darin, das partielle Ergebnis ( v
im vorherigen Beispiel) in eine Iteration (z. B. eine Liste oder ein Tupel) zu setzen und anschließend zu iterieren. Da v
das einzige Element in der Iteration sein wird, haben wir jetzt einen Bezug auf die Ausgabe unserer langsamen Funktion, die nur einmal berechnet wird:
>>> [v for x in range(1000) for v in [f(x)] if v > 10]
[16, 25, 36, ...]
In der Praxis kann die Logik des Codes jedoch komplizierter sein, und es ist wichtig, dass er lesbar bleibt. Im Allgemeinen wird eine separate Generatorfunktion gegenüber einem komplexen Einzeiler empfohlen:
>>> def process_prime_numbers(iterable):
... for x in iterable:
... if is_prime(x):
... yield f(x)
...
>>> [x for x in process_prime_numbers(range(1000)) if x > 10]
[11, 13, 17, 19, ...]
Eine weitere Möglichkeit , die Berechnung zu verhindern , f(x)
mehrfach ist es, die verwenden @functools.lru_cache()
(Python 3.2 +) Dekorateur auf f(x)
. Auf diese Weise wird, da die Ausgabe von f
für die Eingabe x
bereits einmal berechnet wurde, der zweite Funktionsaufruf des ursprünglichen Listenverständnisses so schnell wie eine Wörterbuchsuche. Bei diesem Ansatz wird zur Verbesserung der Effizienz Memoization verwendet, die mit der Verwendung von Generatorausdrücken vergleichbar ist.
Angenommen, Sie müssen eine Liste abflachen
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
Einige der Methoden könnten sein:
reduce(lambda x, y: x+y, l)
sum(l, [])
list(itertools.chain(*l))
Listenverständnis würde jedoch die beste zeitliche Komplexität bieten.
[item for sublist in l for item in sublist]
Die auf + basierenden Verknüpfungen (einschließlich der implizierten Verwendung in summe) sind notwendigerweise O (L ^ 2), wenn L-Unterlisten vorhanden sind. Da die Zwischenergebnisliste immer länger wird, wird bei jedem Schritt ein neues Zwischenergebnislistenobjekt angezeigt zugewiesen, und alle Elemente des vorherigen Zwischenergebnisses müssen kopiert werden (sowie einige neue am Ende hinzugefügt). Also (zur Vereinfachung und ohne tatsächlichen Verlust der Allgemeinheit) sagen Sie, dass Sie L Unterlisten von jeweils I-Elementen haben: Die ersten I-Elemente werden L-1-mal hin und her kopiert, die zweiten I-Elemente L-2-mal usw. Gesamtzahl der Kopien ist I mal die Summe von x für x von 1 bis L ausgeschlossen, dh I * (L ** 2) / 2.
Das Listenverständnis generiert nur einmal eine Liste und kopiert jedes Element (vom ursprünglichen Wohnort bis zur Ergebnisliste) ebenfalls genau einmal.
Verständnis für Tupel
Die for
Klausel eines Listenverständnisses kann mehrere Variablen angeben:
[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [3, 7, 11]
[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# Out: [3, 7, 11]
Das ist wie bei regulären for
Schleifen:
for x, y in [(1,2), (3,4), (5,6)]:
print(x+y)
# 3
# 7
# 11
Wenn der Ausdruck, mit dem das Verständnis beginnt, ein Tupel ist, muss er jedoch in Klammern gesetzt werden:
[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
# SyntaxError: invalid syntax
[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [(1, 2), (3, 4), (5, 6)]
Zählen von Vorkommnissen anhand des Verständnisses
Wenn wir die Anzahl der Elemente in einem iterierbaren Element zählen möchten, die bestimmte Bedingungen erfüllen, können wir das Verständnis nutzen, um eine idiomatische Syntax zu erzeugen:
# Count the numbers in `range(1000)` that are even and contain the digit `9`:
print (sum(
1 for x in range(1000)
if x % 2 == 0 and
'9' in str(x)
))
# Out: 95
Das Grundkonzept kann wie folgt zusammengefasst werden:
- Iteriere über die Elemente im
range(1000)
. - Verketten Sie alle erforderlichen
if
Bedingungen. - Verwenden Sie 1 als Ausdruck , um eine 1 für jeden Artikel zurückzugeben, der die Bedingungen erfüllt.
- Fassen Sie alle
1
s zusammen, um die Anzahl der Elemente zu bestimmen, die die Bedingungen erfüllen.
Hinweis : Hier sammeln wir nicht die 1
in einer Liste (beachten Sie das Fehlen eckiger Klammern), sondern übergeben die 1 direkt an die sum
, die sie aufsummiert. Dies wird als Generatorausdruck bezeichnet , der einem Verständnis ähnlich ist.
Typen in einer Liste ändern
Quantitative Daten werden häufig als Strings eingelesen, die vor der Verarbeitung in numerische Typen umgewandelt werden müssen. Die Typen aller Listenelemente können entweder mit List Comprehension oder der Funktion map()
konvertiert werden .
# Convert a list of strings to integers.
items = ["1","2","3","4"]
[int(item) for item in items]
# Out: [1, 2, 3, 4]
# Convert a list of strings to float.
items = ["1","2","3","4"]
map(float, items)
# Out:[1.0, 2.0, 3.0, 4.0]