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:

Python 2.x 2.7
{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.

Python 2.x 2.7
{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

Python 2.x 2.7
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}
Python 2.x 2.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.

Python 3.x 3.5
{**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]
Python 2.x 2.4
# 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 kehrt generator .

  • generator können nicht indiziert werden und verwenden die next 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 .


Python 2.x 2.4
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
Python 3.x 3.0

HINWEIS: Die Funktion g.next() sollte durch next(g) und xrange mit range da Iterator.next() und xrange() 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
"""
Python 2.x 2.4
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.

Python 2.x 2.7
# 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'])

Live Demo

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:

Python 2.x 2.2
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:

  1. Iteriere über die Elemente im range(1000) .
  2. Verketten Sie alle erforderlichen if Bedingungen.
  3. Verwenden Sie 1 als Ausdruck , um eine 1 für jeden Artikel zurückzugeben, der die Bedingungen erfüllt.
  4. 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] 


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow