5. Datenstrukturen
******************

In diesem Kapitel werden einige Dinge, die du bereits kennengelernt
hast, näher erläutert, und es werden zudem einige neue Aspekte
hinzugefügt.


5.1. Mehr zum Thema Listen
==========================

Der Datentyp list verfügt über einige weitere Methoden. Hier sind alle
Methoden von Listenobjekten aufgeführt:

list.append(value, /)

   Füge einen Eintrag am Ende der Liste hinzu. Ähnlich wie bei
   "a[len(a):] = [x]".

list.extend(iterable, /)

   Erweitere die Liste, indem du alle Elemente aus der iterierbaren
   Struktur anhängst. Ähnlich wie bei "a[len(a):] = iterable".

list.insert(index, value, /)

   Fügt ein Element an einer bestimmten Position ein. Das erste
   Argument ist der Index des Elements, vor dem eingefügt werden soll.
   Daher fügt "a.insert(0, x)" am Anfang der Liste ein, und
   "a.insert(len(a), x)" entspricht "a.append(x)".

list.remove(value, /)

   Entferne das erste Element aus der Liste, dessen Wert *value*
   entspricht. Wenn kein solches Element vorhanden ist, wird eine
   "ValueError" ausgelöst.

list.pop(index=-1, /)

   Entfernt das Element an der angegebenen Position aus der Liste und
   gibt es zurück. Wenn kein Index angegeben wird, entfernt "a.pop()"
   das letzte Element der Liste und gibt es zurück. Es löst eine
   "IndexError" aus, wenn die Liste leer ist oder der Index außerhalb
   des Listenbereichs liegt.

list.clear()

   Entferne alle Elemente aus der Liste. Ähnlich wie bei "del a[:]" .

list.index(value[, start[, stop]])

   Gibt den nullbasierten Index des ersten Vorkommens von *value* in
   der Liste zurück. Löst eine Ausnahme vom Typ "ValueError" aus, wenn
   kein entsprechendes Element vorhanden ist.

   Die optionalen Argumente *start* und *end* werden wie in der Slice-
   Notation interpretiert und dienen dazu, die Suche auf eine
   bestimmte Teilfolge der Liste zu beschränken. Der zurückgegebene
   Index wird relativ zum Anfang der vollständigen Folge und nicht
   relativ zum Argument *start* berechnet.

list.count(value, /)

   Gibt die Anzahl der Vorkommen von *value* in der Liste zurück.

list.sort(*, key=None, reverse=False)

   Sortiere die Elemente der Liste an Ort und Stelle (die Argumente
   können zur Anpassung der Sortierung verwendet werden. Eine
   Erläuterung findest du unter "sorted()" ).

list.reverse()

   Die Elemente der Liste an Ort und Stelle in umgekehrter Reihenfolge
   anordnen.

list.copy()

   Gibt eine flache Kopie der Liste zurück. Ähnlich wie bei "a[:]" .

Ein Beispiel, das die meisten Methoden der Liste verwendet:

   >>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
   >>> fruits.count('apple')
   2
   >>> fruits.count('tangerine')
   0
   >>> fruits.index('banana')
   3
   >>> fruits.index('banana', 4)  # Die nächste Banane ab Position 4 finden
   6
   >>> fruits.reverse()
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
   >>> fruits.append('grape')
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
   >>> fruits.sort()
   >>> fruits
   ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
   >>> fruits.pop()
   'pear'

Vielleicht ist dir aufgefallen, dass bei Methoden wie "insert",
"remove" oder "sort", die lediglich die Liste verändern, kein
Rückgabewert ausgegeben wird – sie geben den Standardwert "None"
zurück. [1] Dies ist ein Entwurfsprinzip für alle veränderbaren
Datenstrukturen in Python.

Außerdem wird dir vielleicht auffallen, dass nicht alle Daten sortiert
oder verglichen werden können. So lässt sich beispielsweise "[None,
'hello', 10]" nicht sortieren, da Ganzzahlen nicht mit Zeichenketten
verglichen werden können und "None" nicht mit anderen Typen verglichen
werden kann. Zudem gibt es einige Typen, für die keine definierte
Ordnungsrelation existiert. So ist beispielsweise "3+4j < 5+7j" kein
gültiger Vergleich.


5.1.1. Listen als Stapel verwenden
----------------------------------

Die Listenmethoden machen es sehr einfach, eine Liste als Stapel zu
verwenden, bei dem das zuletzt hinzugefügte Element als erstes wieder
abgerufen wird ("Last-in, First-out").  Um ein Element oben auf den
Stapel zu setzen, verwende "append()".  Um ein Element von der Spitze
des Stapels abzurufen, verwende "pop()" ohne expliziten Index.
Beispiel:

   >>> stack = [3, 4, 5]
   >>> stack.append(6)
   >>> stack.append(7)
   >>> stack
   [3, 4, 5, 6, 7]
   >>> stack.pop()
   7
   >>> stack
   [3, 4, 5, 6]
   >>> stack.pop()
   6
   >>> stack.pop()
   5
   >>> stack
   [3, 4]


5.1.2. Listen als Warteschlangen verwenden
------------------------------------------

Es ist auch möglich, eine Liste als Warteschlange zu verwenden, bei
der das zuerst hinzugefügte Element als erstes wieder abgerufen wird
("First-in, First-out"). Allerdings sind Listen für diesen Zweck nicht
effizient. Während das Anhängen und Entnehmen am Ende einer Liste
schnell erfolgt, ist das Einfügen oder Entnehmen am Anfang einer Liste
langsam (da alle anderen Elemente um eine Position verschoben werden
müssen). Weitere Informationen findest du unter Zeitkomplexität von
Operationen auf eingebauten Typen.

Um eine Warteschlange zu implementieren, verwende "collections.deque",
das für schnelles Hinzufügen und Entnehmen von beiden Enden ausgelegt
ist.  Beispiel:

   >>> from collections import deque
   >>> queue = deque(["Eric", "John", "Michael"])
   >>> queue.append("Terry")           # Terry kommt an
   >>> queue.append("Graham")          # Graham kommt an
   >>> queue.popleft()                 # Der Erste, der angekommen ist, verlässt nun die Warteschlange
   'Eric'
   >>> queue.popleft()                 # Der Zweite, der angekommen ist, verlässt nun die Warteschlange
   'John'
   >>> queue                           # Verbleibende Warteschlange in der Reihenfolge der Ankunft
   deque(['Michael', 'Terry', 'Graham'])


5.1.3. Listen-Abstraktion
-------------------------

Listen-Abstraktion bieten eine prägnante Möglichkeit, Listen zu
erstellen. Häufige Anwendungsfälle sind die Erstellung neuer Listen,
bei denen jedes Element das Ergebnis bestimmter Operationen ist, die
auf jedes Element einer anderen Sequenz oder eines anderen
iterierbaren Objekts angewendet werden, oder die Erstellung einer
Teilsequenz aus den Elementen, die eine bestimmte Bedingung erfüllen.

Nehmen wir zum Beispiel an, wir möchten eine Liste von Quadraten
erstellen, etwa wie folgt:

   >>> squares = []
   >>> for x in range(10):
   ...     squares.append(x**2)
   ...
   >>> squares
   [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Beachte, dass dadurch eine Variable namens "x" angelegt (oder
überschrieben) wird, die auch nach Abschluss der Schleife noch
vorhanden ist. Wir können die Liste der Quadrate ohne Nebenwirkungen
wie folgt berechnen:

   Quadrate = list(map(lambda x: x**2, range(10)))

oder, gleichbedeutend:

   Quadrate = [x**2 for x in range(10)]

was prägnanter und besser lesbar ist.

Eine Listenkomprimierung besteht aus Klammern, die einen Ausdruck
enthalten, gefolgt von einer "for"-Klausel und anschließend null oder
mehr "for"- oder "if"-Klauseln. Das Ergebnis ist eine neue Liste, die
sich aus der Auswertung des Ausdrucks im Kontext der darauf folgenden
"for"- und "if"-Klauseln ergibt. Diese Listenkomprimierung kombiniert
beispielsweise die Elemente zweier Listen, sofern sie nicht identisch
sind:

   >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

und das entspricht:

   >>> combs = []
   >>> for x in [1,2,3]:
   ...     for y in [3,1,4]:
   ...         if x != y:
   ...             combs.append((x, y))
   ...
   >>> combs
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Beachte, dass die Reihenfolge der Anweisungen "for" und "if" in beiden
Codeausschnitten identisch ist.

Wenn es sich bei dem Ausdruck um ein Tupel handelt (z. B. "(x, y)" im
vorherigen Beispiel), muss es in Klammern gesetzt werden.

   >>> vec = [-4, -2, 0, 2, 4]
   >>> # Erstelle eine neue Liste, in der die Werte verdoppelt sind
   >>> [x*2 for x in vec]
   [-8, -4, 0, 4, 8]
   >>> # Filtere die Liste, um negative Zahlen auszuschließen
   >>> [x for x in vec if x >= 0]
   [0, 2, 4]
   >>> # Eine Funktion auf alle Elemente anwenden
   >>> [abs(x) for x in vec]
   [4, 2, 0, 2, 4]
   >>> # Eine Methode auf jedes Element anwenden
   >>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
   >>> [weapon.strip() for weapon in freshfruit]
   ['banana', 'loganberry', 'passion fruit']
   >>> # Eine Liste von 2-Tupeln wie (Zahl, Quadrat) erstellen
   >>> [(x, x**2) for x in range(6)]
   [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
   >>> # Das Tupel muss in Klammern gesetzt werden, sonst wird ein Fehler ausgelöst
   >>> [x, x**2 for x in range(6)]
     File "<stdin>", line 1
       [x, x**2 for x in range(6)]
        ^^^^^^^
   SyntaxError: did you forget parentheses around the comprehension target?
   >>> # Eine Liste mithilfe einer List Comprehension mit zwei "for"-Schleifen abflachen
   >>> vec = [[1,2,3], [4,5,6], [7,8,9]]
   >>> [num for elem in vec for num in elem]
   [1, 2, 3, 4, 5, 6, 7, 8, 9]

Listen-Abstraktionen können komplexe Ausdrücke und verschachtelte
Funktionen enthalten:

   >>> from math import pi
   >>> [str(round(pi, i)) for i in range(1, 6)]
   ['3.1', '3.14', '3.142', '3.1416', '3.14159']


5.1.4. Verschachtelte Listen-Abstraktion
----------------------------------------

Der erste Ausdruck in einer Listen-Abstraktion kann ein beliebiger
Ausdruck sein, einschließlich einer weiteren Listen-Abstraktion.

Betrachte das folgende Beispiel einer 3×4-Matrix, die als Liste aus
drei Listen der Länge 4 implementiert ist:

   >>> matrix = [
   ...     [1, 2, 3, 4],
   ...     [5, 6, 7, 8],
   ...     [9, 10, 11, 12],
   ... ]

Die folgende Listen-Abstraktion transponiert Zeilen und Spalten:

   >>> [[row[i] for row in matrix] for i in range(4)]
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Wie wir im vorigen Abschnitt gesehen haben, wird die innere
Listenauswertung im Kontext des darauf folgenden "for" ausgewertet,
sodass dieses Beispiel gleichbedeutend ist mit:

   >>> transposed = []
   >>> for i in range(4):
   ...     transposed.append([row[i] for row in matrix])
   ...
   >>> transposed
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

was wiederum dasselbe ist wie:

   >>> transposed = []
   >>> for i in range(4):
   ...     # Die folgenden 3 Zeilen implementieren die verschachtelte Listenkomposition
   ...     transposed_row = []
   ...     for row in matrix:
   ...         transposed_row.append(row[i])
   ...     transposed.append(transposed_row)
   ...
   >>> transposed
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

In der Praxis solltest du integrierte Funktionen komplexen
Ablaufanweisungen vorziehen. Die Funktion "zip()" eignet sich
hervorragend für diesen Anwendungsfall:

   >>> list(zip(*matrix))
   [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

Weitere Informationen zum Sternchen in dieser Zeile findest du unter
Entpacken von Argumentlisten.


5.1.5. Entpacken in Listen und List Comprehensions
--------------------------------------------------

Der Abschnitt Entpacken von Argumentlisten beschreibt die Verwendung
von "*", um die Elemente eines iterierbaren Objekts zu "entpacken" und
jedes einzeln als Argument an eine Funktion zu übergeben. Entpacken
kann auch in anderen Kontexten verwendet werden, zum Beispiel beim
Erstellen von Listen. Wird bei der Angabe von Listenelementen einem
Ausdruck ein "*" vorangestellt, wird das Ergebnis dieses Ausdrucks
entpackt, wobei jedes seiner Elemente der Liste hinzugefügt wird, die
wir gerade erstellen:

   >>> x = [1, 2, 3]
   >>> [0, *x, 4, 5, 6]
   [0, 1, 2, 3, 4, 5, 6]

Dies funktioniert nur, wenn der Ausdruck nach dem "*" zu einem
iterierbaren Objekt ausgewertet wird. Der Versuch, ein nicht
iterierbares Objekt zu entpacken, löst eine Ausnahme aus:

   >>> x = 1
   >>> [0, *x, 2, 3, 4]
   Traceback (most recent call last):
     File "<python-input-1>", line 1, in <module>
       [0, *x, 2, 3, 4]
   TypeError: Value after * must be an iterable, not int

Entpacken kann auch in List Comprehensions verwendet werden, um eine
neue Liste zu erstellen, die die Verkettung einer beliebigen Anzahl
von iterierbaren Objekten darstellt:

   >>> x = [[1, 2, 3], [4, 5, 6], [], [7], [8, 9]]
   >>> [*element for element in x]
   [1, 2, 3, 4, 5, 6, 7, 8, 9]

Beachte, dass dabei jedes Element aus "x" entpackt wird. Dies
funktioniert für beliebige iterierbare Objekte, nicht nur für Listen:

   >>> x = [[1, 2, 3], 'cat', {'spam': 'eggs'}]
   >>> [*element for element in x]
   [1, 2, 3, 'c', 'a', 't', 'spam']

Wenn die Objekte in "x" jedoch nicht iterierbar sind, würde dieser
Ausdruck erneut eine Ausnahme auslösen.


5.2. Die Erklärung der "del"
============================

Es gibt eine Möglichkeit, ein Element anhand seines Indexes statt
seines Wertes aus einer Liste zu entfernen: die Anweisung "del". Diese
unterscheidet sich von der Methode "pop()", die einen Wert zurückgibt.
Die Anweisung "del" kann auch verwendet werden, um Teilmengen aus
einer Liste zu entfernen oder die gesamte Liste zu leeren (was wir
zuvor durch die Zuweisung einer leeren Liste zur Teilmenge getan
haben). Zum Beispiel:

   >>> a = [-1, 1, 66.25, 333, 333, 1234.5]
   >>> del a[0]
   >>> a
   [1, 66.25, 333, 333, 1234.5]
   >>> del a[2:4]
   >>> a
   [1, 66.25, 1234.5]
   >>> del a[:]
   >>> a
   []

"del" kann auch zum Löschen ganzer Variablen verwendet werden:

   >>> del a

Der Verweis auf den Namen "a" ist hier ein Fehler (zumindest solange,
bis ihm ein anderer Wert zugewiesen wird). Wir werden später noch
weitere Verwendungsmöglichkeiten für "del" finden.


5.3. Tupel und Sequenzen
========================

Wir haben gesehen, dass Listen und Zeichenketten viele gemeinsame
Eigenschaften haben, wie beispielsweise Indizierungs- und
Ausschnittoperationen. Sie sind zwei Beispiele für
*Sequenz*-Datentypen (siehe Sequenztypen – list, tuple, range). Da
Python eine sich weiterentwickelnde Sprache ist, können weitere
Sequenz-Datentypen hinzukommen. Es gibt außerdem einen weiteren
standardmäßigen Sequenz-Datentyp: das *Tupel*.

Ein Tupel besteht aus einer Reihe von Werten, die durch Kommas
getrennt sind, zum Beispiel:

   >>> t = 12345, 54321, 'hello!'
   >>> t[0]
   12345
   >>> t
   (12345, 54321, 'hello!')
   >>> # Tupel können verschachtelt werden:
   >>> u = t, (1, 2, 3, 4, 5)
   >>> u
   ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
   >>> # Tupel sind unveränderlich:
   >>> t[0] = 88888
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: 'tuple' object does not support item assignment
   >>> # aber sie können veränderliche Objekte enthalten:
   >>> v = ([1, 2, 3], [3, 2, 1])
   >>> v
   ([1, 2, 3], [3, 2, 1])
   >>> # sie unterstützen Entpacken genau wie Listen:
   >>> x = [1, 2, 3]
   >>> 0, *x, 4
   (0, 1, 2, 3, 4)

Wie du siehst, werden Tupel in der Ausgabe immer in Klammern gesetzt,
damit verschachtelte Tupel korrekt interpretiert werden. Sie können
mit oder ohne umschließende Klammern eingegeben werden, obwohl
Klammern oft ohnehin erforderlich sind (wenn das Tupel Teil eines
größeren Ausdrucks ist).  Es ist nicht möglich, den einzelnen
Elementen eines Tupels Werte zuzuweisen. Es ist jedoch möglich, Tupel
zu erstellen, die veränderbare Objekte wie beispielsweise Listen
enthalten.

Auch wenn Tupel auf den ersten Blick Listen ähneln mögen, werden sie
oft in anderen Situationen und für andere Zwecke verwendet. Tupel sind
*unveränderlich* und enthalten in der Regel eine heterogene Folge von
Elementen, auf die über Entpacken (siehe weiter unten in diesem
Abschnitt) oder Indizierung (oder im Fall von "namedtuples" sogar über
Attribute) zugegriffen wird. Listen sind *veränderlich*, und ihre
Elemente sind in der Regel homogen. Der Zugriff erfolgt durch
Iteration über die Liste.

Ein besonderes Problem stellt die Bildung von Tupeln dar, die 0 oder 1
Element enthalten: Die Syntax weist hierfür einige zusätzliche
Besonderheiten auf.  Leere Tupel werden durch ein leeres Klammerpaar
gebildet. Ein Tupel mit einem Element wird gebildet, indem man einem
Wert ein Komma nachsetzt (es reicht nicht aus, einen einzelnen Wert in
Klammern zu setzen). Unschön, aber effektiv.  Zum Beispiel:

   >>> empty = ()
   >>> singleton = 'hello',    # <-- note trailing comma
   >>> len(empty)
   0
   >>> len(singleton)
   1
   >>> singleton
   ('hello',)

Die Anweisung "t = 12345, 54321, 'hello!'" ist ein Beispiel für
*Tupel-Packing*: Die Werte "12345", "54321" und "'hello!'" werden in
einem Tupel zusammengefasst. Auch die umgekehrte Operation ist
möglich:

   >>> x, y, z = t

Dies wird passenderweise als *Sequenz-Entpackung* bezeichnet und
funktioniert für jede beliebige Sequenz auf der rechten Seite. Für die
Sequenz-Entpackung muss die Anzahl der Variablen auf der linken Seite
des Gleichheitszeichens der Anzahl der Elemente in der Sequenz
entsprechen.  Beachte, dass die Mehrfachzuweisung im Grunde nur eine
Kombination aus Tupel-Packing und Sequenz-Unpacking ist.


5.4. Sets
=========

Python enthält außerdem einen Datentyp für Mengen. Eine Menge ist eine
ungeordnete Sammlung ohne doppelte Elemente. Zu den grundlegenden
Anwendungsbereichen gehören die Überprüfung der Zugehörigkeit zu einer
Menge und das Entfernen doppelter Einträge. Mengenobjekte unterstützen
zudem mathematische Operationen wie Vereinigung, Schnittmenge,
Differenz und symmetrische Differenz.

Zum Erstellen von Mengen können geschweifte Klammern oder die Funktion
"set()" verwendet werden. Hinweis: Um eine leere Menge zu erstellen,
musst du "set()" verwenden, nicht "{}"; Letzteres erstellt ein leeres
Wörterbuch, eine Datenstruktur, die wir im nächsten Abschnitt
behandeln.

Da Mengen ungeordnet sind, kann es beim Durchlaufen oder Ausgeben der
Elemente vorkommen, dass diese in einer anderen Reihenfolge angezeigt
werden, als erwartet.

Hier eine kurze Demonstration:

   >>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
   >>> print(basket)                      # zeigt, dass Duplikate entfernt wurden
   {'orange', 'banana', 'pear', 'apple'}
   >>> 'orange' in basket                 # schnelle Zugehörigkeitsprüfung
   True
   >>> 'crabgrass' in basket
   False

   >>> # Demonstration von Mengenoperationen mit eindeutigen Buchstaben aus zwei Wörtern
   >>>
   >>> a = set('abracadabra')
   >>> b = set('alacazam')
   >>> a                                  # eindeutige Buchstaben in a
   {'a', 'r', 'b', 'c', 'd'}
   >>> a - b                              # Buchstaben in a, die nicht in b vorkommen
   {'r', 'd', 'b'}
   >>> a | b                              # Buchstaben in a oder b oder in beiden
   {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
   >>> a & b                              # Buchstaben, die sowohl in a als auch in b vorkommen
   {'a', 'c'}
   >>> a ^ b                              # Buchstaben, die entweder in a oder in b vorkommen, aber nicht in beiden
   {'r', 'd', 'b', 'm', 'z', 'l'}

Ähnlich wie List Comprehensions werden auch Set Comprehensions
unterstützt, einschließlich Comprehensions mit Entpacken:

   >>> a = {x for x in 'abracadabra' if x not in 'abc'}
   >>> a
   {'r', 'd'}

   >>> fruits = [{'apple', 'avocado', 'apricot'}, {'banana', 'blueberry'}]
   >>> {*fruit for fruit in fruits}
   {'blueberry', 'banana', 'avocado', 'apple', 'apricot'}


5.5. Wörterbücher
=================

Ein weiterer nützlicher, in Python integrierter Datentyp ist das
*Wörterbuch* (siehe Mapping-Typen – dict, frozendict). Wörterbücher
werden in anderen Sprachen manchmal als "assoziative Speicher" oder
"assoziative Arrays" bezeichnet.  Im Gegensatz zu Sequenzen, die durch
einen Zahlenbereich indiziert werden, werden Dictionaries durch
*Schlüssel* indiziert, die von jedem unveränderlichen Typ sein können;
Zeichenketten und Zahlen können immer als Schlüssel verwendet werden.
Tupel können als Schlüssel verwendet werden, wenn sie ausschließlich
Zeichenketten, Zahlen oder Tupel enthalten. Enthält ein Tupel direkt
oder indirekt ein veränderbliches Objekt, kann es nicht als Schlüssel
verwendet werden. Listen können nicht als Schlüssel verwendet werden,
da sie durch Indexzuweisungen, Slice-Zuweisungen oder Methoden wie
"append()" und "extend()" an Ort und Stelle verändert werden können.

Man kann sich ein Wörterbuch am besten als eine Menge von
*Schlüssel:Wert*-Paaren vorstellen, wobei die Schlüssel (innerhalb
eines Wörterbuchs) eindeutig sein müssen. Ein Paar geschweifter
Klammern erstellt ein leeres Wörterbuch: "{}". Durch Einfügen einer
durch Kommas getrennten Liste von Schlüssel:Wert-Paaren innerhalb der
geschweiften Klammern werden dem Wörterbuch anfängliche Schlüssel
:Wert-Paare hinzugefügt. Auf diese Weise werden Wörterbücher auch bei
der Ausgabe dargestellt.

Die wichtigsten Operationen in einem Wörterbuch sind das Speichern
eines Werts unter einem bestimmten Schlüssel und das Abrufen des Werts
anhand des Schlüssels. Es ist außerdem möglich, ein Schlüssel-Wert-
Paar mit "del" zu löschen. Wenn du einen Wert unter einem Schlüssel
speicherst, der bereits verwendet wird, wird der alte, diesem
Schlüssel zugeordnete Wert verworfen.

Der Versuch, einen Wert für einen nicht vorhandenen Schlüssel mittels
Indexierung abzurufen ("d[key]"), löst einen "KeyError" aus. Um diesen
Fehler beim Zugriff auf einen möglicherweise nicht vorhandenen
Schlüssel zu vermeiden, verwende stattdessen die Methode "get()", die
"None" (oder einen angegebenen Standardwert) zurückgibt, wenn der
Schlüssel nicht im Wörterbuch enthalten ist.

Wendet man "list(d)" auf ein Wörterbuch an, wird eine Liste aller im
Wörterbuch verwendeten Schlüssel in der Reihenfolge ihrer Einfügung
zurückgegeben (wenn du die Liste sortiert haben möchtest, verwende
stattdessen einfach "sorted(d)"). Um zu prüfen, ob ein einzelner
Schlüssel im Wörterbuch enthalten ist, verwende das Schlüsselwort "in"
.

Hier ist ein kleines Beispiel, bei dem ein Wörterbuch verwendet wird:

   >>> tel = {'jack': 4098, 'sape': 4139}
   >>> tel['guido'] = 4127
   >>> tel
   {'jack': 4098, 'sape': 4139, 'guido': 4127}
   >>> tel['jack']
   4098
   >>> tel['irv']
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   KeyError: 'irv'
   >>> print(tel.get('irv'))
   None
   >>> del tel['sape']
   >>> tel['irv'] = 4127
   >>> tel
   {'jack': 4098, 'guido': 4127, 'irv': 4127}
   >>> list(tel)
   ['jack', 'guido', 'irv']
   >>> sorted(tel)
   ['guido', 'irv', 'jack']
   >>> 'guido' in tel
   True
   >>> 'jack' not in tel
   False

Der Konstruktor "dict()" erstellt Wörterbücher direkt aus Sequenzen
von Schlüssel-Wert-Paaren:

   >>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   {'sape': 4139, 'guido': 4127, 'jack': 4098}

Darüber hinaus können Dict-Comprehensions verwendet werden, um
Wörterbücher aus beliebigen Schlüssel- und Wert-Ausdrücken zu
erstellen:

   >>> {x: x**2 for x in (2, 4, 6)}
   {2: 4, 4: 16, 6: 36}

Und Dictionary-Entpacken (mittels "**") kann verwendet werden, um
mehrere Dictionaries zusammenzuführen:

   >>> odds = {i: i**2 for i in (1, 3, 5)}
   >>> evens = {i: i**2 for i in (2, 4, 6)}
   >>> {**odds, **evens}
   {1: 1, 3: 9, 5: 25, 2: 4, 4: 16, 6: 36}

   >>> all_values = [odds, evens, {0: 0}]
   >>> {**i for i in all_values}
   {1: 1, 3: 9, 5: 25, 2: 4, 4: 16, 6: 36, 0: 0}

Wenn es sich bei den Schlüsseln um einfache Zeichenfolgen handelt, ist
es manchmal einfacher, Paare mithilfe von Schlüsselwortargumenten
anzugeben:

   >>> dict(sape=4139, guido=4127, jack=4098)
   {'sape': 4139, 'guido': 4127, 'jack': 4098}


5.6. Schleifentechniken
=======================

Beim Durchlaufen von Wörterbüchern können der Schlüssel und der
zugehörige Wert mithilfe der Methode "items()" gleichzeitig abgerufen
werden.

   >>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
   >>> for k, v in knights.items():
   ...     print(k, v)
   ...
   gallahad the pure
   robin the brave

Beim Durchlaufen einer Sequenz können der Positionsindex und der
zugehörige Wert mithilfe der Funktion "enumerate()" gleichzeitig
abgerufen werden.

   >>> for i, v in enumerate(['tic', 'tac', 'toe']):
   ...     print(i, v)
   ...
   0 tic
   1 tac
   2 toe

Um zwei oder mehr Sequenzen gleichzeitig zu durchlaufen, können die
Einträge mit der Funktion "zip()" gepaart werden.

   >>> questions = ['name', 'quest', 'favorite color']
   >>> answers = ['lancelot', 'the holy grail', 'blue']
   >>> for q, a in zip(questions, answers):
   ...     print('What is your {0}?  It is {1}.'.format(q, a))
   ...
   What is your name?  It is lancelot.
   What is your quest?  It is the holy grail.
   What is your favorite color?  It is blue.

Um eine Folge in umgekehrter Reihenfolge zu durchlaufen, gib zunächst
die Folge in Vorwärtsrichtung an und rufe anschließend die Funktion
"reversed()" auf.

   >>> for i in reversed(range(1, 10, 2)):
   ...     print(i)
   ...
   9
   7
   5
   3
   1

Um eine Folge in sortierter Reihenfolge zu durchlaufen, verwende die
Funktion "sorted()", die eine neue sortierte Liste zurückgibt, während
die Quelle unverändert bleibt.

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for i in sorted(basket):
   ...     print(i)
   ...
   apple
   apple
   banana
   orange
   orange
   pear

Die Anwendung von "set()" auf eine Sequenz entfernt doppelte Elemente.
Die Verwendung von "sorted()" in Kombination mit "set()" auf eine
Sequenz ist eine gängige Methode, um die eindeutigen Elemente der
Sequenz in sortierter Reihenfolge durchzugehen.

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for f in sorted(set(basket)):
   ...     print(f)
   ...
   apple
   banana
   orange
   pear

Manchmal ist es verlockend, eine Liste zu ändern, während man sie in
einer Schleife durchläuft. Oft ist es jedoch einfacher und sicherer,
stattdessen eine neue Liste zu erstellen.

   >>> import math
   >>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
   >>> filtered_data = []
   >>> for value in raw_data:
   ...     if not math.isnan(value):
   ...         filtered_data.append(value)
   ...
   >>> filtered_data
   [56.2, 51.7, 55.3, 52.5, 47.8]


5.7. Mehr zu den Bedingungen
============================

Die in den Anweisungen "while" und "if" verwendeten Bedingungen können
beliebige Operatoren enthalten, nicht nur Vergleichsoperatoren.

Die Vergleichsoperatoren "in" und "not in" sind
Zugehörigkeitsprüfungen, die feststellen, ob ein Wert in einem
Container enthalten ist (oder nicht). Die Operatoren "is" und "is not"
vergleichen, ob zwei Objekte tatsächlich dasselbe Objekt sind. Alle
Vergleichsoperatoren haben dieselbe Priorität, die niedriger ist als
die aller numerischen Operatoren.

Vergleiche können verkettet werden. Beispielsweise prüft "a < b == c",
ob "a" kleiner ist als "b" und ob darüber hinaus "b" gleich "c" ist.

Vergleiche können mithilfe der Booleschen Operatoren "and" und "or"
kombiniert werden, und das Ergebnis eines Vergleichs (oder eines
beliebigen anderen Booleschen Ausdrucks) kann mit "not" negiert
werden. Diese haben eine niedrigere Priorität als
Vergleichsoperatoren. Unter ihnen hat "not" die höchste Priorität und
"or" die niedrigste, sodass "A and not B or C" gleichbedeutend ist mit
"(A and (not B)) or C". Wie immer können Klammern verwendet werden, um
die gewünschte Zusammensetzung auszudrücken.

Die Booleschen Operatoren "and" und "or" sind sogenannte
*Kurzschluss*-Operatoren: Deine Argumente werden von links nach rechts
ausgewertet, und die Auswertung wird beendet, sobald das Ergebnis
feststeht.  Sind beispielsweise "A" und "C" wahr, "B" jedoch falsch,
so wertet "A and B and C" den Ausdruck "C" nicht aus.  Wird ein
Kurzschlussoperator als allgemeiner Wert und nicht als Boolescher Wert
verwendet, ist der Rückgabewert das zuletzt ausgewertete Argument.

Es ist möglich, das Ergebnis eines Vergleichs oder eines anderen
booleschen Ausdrucks einer Variablen zuzuweisen.  Beispiel:

   >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
   >>> non_null = string1 or string2 or string3
   >>> non_null
   'Trondheim'

Beachte dass in Python – anders als in C – Zuweisungen innerhalb von
Ausdrücken explizit mit dem Walross-Operator ":=" erfolgen müssen.
Dadurch wird eine häufige Problemklasse vermieden, die in C-Programmen
auftritt: die Eingabe von "=" in einem Ausdruck, obwohl eigentlich
"==" gemeint war.


5.8. Vergleich von Sequenzen und anderen Typen
==============================================

Sequenzobjekte können in der Regel mit anderen Objekten desselben
Sequenztyps verglichen werden. Der Vergleich erfolgt nach
*lexikografischer* Reihenfolge: Zunächst werden die ersten beiden
Elemente verglichen. Unterscheiden sie sich, bestimmt dies das
Ergebnis des Vergleichs. Sind sie gleich, werden die nächsten beiden
Elemente verglichen und so weiter, bis eine der beiden Sequenzen
erschöpft ist. Sind zwei zu vergleichende Elemente selbst Sequenzen
desselben Typs, wird der lexikografische Vergleich rekursiv
durchgeführt. Sind alle Elemente zweier Sequenzen gleich, gelten die
Sequenzen als gleich. Ist eine Sequenz eine anfängliche Teilsequenz
der anderen, ist die kürzere Sequenz die kleinere (geringere).  Bei
der lexikografischen Sortierung von Zeichenketten wird die Unicode-
Codepunktnummer zur Sortierung einzelner Zeichen verwendet. Einige
Beispiele für Vergleiche zwischen Sequenzen desselben Typs:

   (1, 2, 3)              < (1, 2, 4)
   [1, 2, 3]              < [1, 2, 4]
   'ABC' < 'C' < 'Pascal' < 'Python'
   (1, 2, 3, 4)           < (1, 2, 4)
   (1, 2)                 < (1, 2, -1)
   (1, 2, 3)             == (1,0, 2,0, 3,0)
   (1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Beachte, dass der Vergleich von Objekten unterschiedlicher Typen mit
"<" oder ">" zulässig ist, sofern die Objekte über entsprechende
Vergleichsmethoden verfügen. Beispielsweise werden gemischte
numerische Typen anhand ihres numerischen Werts verglichen, sodass 0
gleich 0,0 ist usw. Andernfalls liefert der Interpreter keine
willkürliche Reihenfolge, sondern löst eine "TypeError"-Ausnahme aus.

-[ Fußnoten ]-

[1] Andere Sprachen geben möglicherweise das geänderte Objekt zurück,
    was eine Methodenverkettung ermöglicht, wie beispielsweise
    "d->insert("a")->remove("b")->sort();".
