13.2 Listen – list
In diesem Abschnitt werden Sie den ersten veränderbaren (mutablen) Datentyp, die Liste, im Detail kennenlernen. Anders als die sequenziellen Datentypen str, bytes und bytearray, die nur gleichartige Elemente (Zeichen) speichern können, sind Listen für die Verwaltung beliebiger Instanzen auch unterschiedlicher Datentypen geeignet. Eine Liste kann also durchaus Zahlen, Strings oder auch weitere Listen als Elemente enthalten.
Eine neue Liste können Sie dadurch erzeugen, dass Sie eine Aufzählung ihrer Elemente in eckige Klammern [] schreiben:
>>> l = [1, 0.5, "String", 2]
Die Liste l enthält nun zwei Ganzzahlen, eine Gleitkommazahl und einen String.
[»] Hinweis
Eine Liste kann auch mit einer List Comprehension erzeugt werden. Dabei werden nicht alle Elemente der Liste explizit aufgelistet, sondern über eine Bildungsvorschrift ähnlich einer for-Schleife erzeugt. Die folgende List Comprehension erzeugt beispielsweise eine Liste mit den Quadraten der Zahlen von 0 bis 9.
>>> [i*i for i in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Näheres zu List Comprehensions erfahren Sie in Abschnitt 23.1.1.
Beim Erstellen einer Liste kann auf Unpacking zurückgegriffen werden:
>>> [1, 2, *[3, 4]]
[1, 2, 3, 4]
Nähere Informationen zum Thema Unpacking finden Sie in Abschnitt Abschnitt 13.3.1.
Da es sich bei dem Listentyp, der innerhalb von Python den Namen list hat, um einen sequenziellen Datentyp handelt, können alle im letzten Abschnitt beschriebenen Methoden und Verfahren auf ihn angewandt werden. In Abschnitt 13.1 finden Sie eine Tabelle mit den Operationen, die für alle sequenziellen Datentypen verfügbar sind.
Im Gegensatz zu den bisher besprochenen sequenziellen Datentypen kann sich der Inhalt einer Liste auch nach ihrer Erzeugung ändern, weshalb eine Reihe weiterer Operatoren und Methoden für sie verfügbar ist:
Operator | Wirkung | Abschnitt |
---|---|---|
s[i] = x | Das Element von s mit dem Index i wird durch x ersetzt. | Abschnitt 13.2.1 |
s[i:j] = t | Der Teil s[i:j] wird durch t ersetzt. Dabei muss t iterierbar sein. | Abschnitt 13.2.2 |
s[i:j:k] = t | Die Elemente von s[i:j:k] werden durch die von t ersetzt. | Abschnitt 13.2.2 |
del s[i] | Das i-te Element von s wird entfernt. | Abschnitt 13.2.3 |
del s[i:j] | Der Teil s[i:j] wird aus s entfernt. Das ist äquivalent zu s[i:j] = []. | Abschnitt 13.2.3 |
del s[i:j:k] | Die Elemente der Teilfolge s[i:j:k] werden aus s entfernt. | Abschnitt 13.2.3 |
Wir werden diese Operatoren der Reihe nach mit kleinen Beispielen erklären.
13.2.1 Verändern eines Wertes innerhalb der Liste – Zuweisung mit []
Sie können Elemente einer Liste durch andere ersetzen, wenn Sie ihren Index kennen:
>>> s = [1, 2, 3, 4, 5, 6, 7]
>>> s[3] = 1337
>>> s
[1, 2, 3, 1337, 5, 6, 7]
Diese Methode eignet sich allerdings nicht, um Elemente in die Liste einzufügen. Es können nur bereits bestehende Elemente ersetzt werden, die Länge der Liste bleibt unverändert.
13.2.2 Ersetzen von Teillisten und Einfügen neuer Elemente – Zuweisung mit []
Es ist möglich, eine ganze Teilliste durch andere Elemente zu ersetzen. Dazu schreiben Sie den zu ersetzenden Teil der Liste wie beim Slicing auf, wobei er aber auf der linken Seite einer Zuweisung stehen muss:
>>> einkaufen = ["Brot", "Eier", "Milch", "Fisch", "Mehl"]
>>> einkaufen[1:3] = ["Wasser", "Wurst"]
>>> einkaufen
['Brot', 'Wasser', 'Wurst', 'Fisch', 'Mehl']
Die Liste, die eingefügt werden soll, kann mehr oder weniger Elemente als der zu ersetzende Teil haben und sogar ganz leer sein.
Man kann wie beim Slicing eine Schrittweite angeben. Im folgenden Beispiel wird jedes dritte Element der Teilsequenz s[2:11] durch das entsprechende Element aus ["A", "B", "C"] ersetzt:
>>> s = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> s[2:9:3] = ["A", "B", "C"]
>>> s
[0, 1, 'A', 3, 4, 'B', 6, 7, 'C', 9, 10]
Wird eine Schrittweite angegeben, muss die Sequenz auf der rechten Seite der Zuweisung genauso viele Elemente wie die Teilsequenz auf der linken Seite haben. Ist das nicht der Fall, wird ein ValueError erzeugt.
13.2.3 Elemente und Teillisten löschen – del zusammen mit []
Um einen einzelnen Wert aus einer Liste zu entfernen, verwenden Sie den del-Operator:
>>> s = [26, 7, 1987]
>>> del s[0]
>>> s
[7, 1987]
Auf diese Weise lassen sich auch ganze Teillisten entfernen:
>>> s = [9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> del s[3:6]
>>> s
[9, 8, 7, 3, 2, 1]
Für das Entfernen von Teilen einer Liste wird auch die Schrittfolge der Slicing-Notation unterstützt. Im folgenden Beispiel werden damit alle Elemente mit geradem Index entfernt:
>>> s = ["a","b","c","d","e","f","g","h","i","j"]
>>> del s[::2]
>>> s
['b', 'd', 'f', 'h', 'j']
13.2.4 Methoden von list-Instanzen
Nachdem nun die Operatoren für Listen behandelt worden sind, wenden wir uns den Methoden einer Liste zu. In Tabelle 13.4 sind s und t Listen, i, j und k sind Ganzzahlen, und x ist eine beliebige Instanz:[ 42 ](Achtung: Wenn in der linken Spalte Parameter mit eckigen Klammern eingeklammert sind, bedeutet dies nach wie vor, dass es sich um optionale Parameter handelt. Diese eckigen Klammern haben nichts mit dem Erzeugen einer neuen Liste zu tun. )
Methode | Wirkung |
---|---|
s.append(x) | Hängt x ans Ende der Liste s an. |
s.extend(t) | Hängt alle Elemente der Liste t ans Ende der Liste s an. |
s.insert(i, x) | Fügt x an der Stelle i in die Liste s ein. Anschließend hat s[i] den Wert von x, wobei alle folgenden Elemente um eine Stelle nach hinten aufrücken. |
s.pop([i]) | Gibt das i-te Element der Liste s zurück und entfernt es aus s. Ist i nicht angegeben, wird das letzte Element genommen. |
s.remove(x) | Entfernt das erste Vorkommen von x aus der Liste s. |
s.reverse() | Kehrt die Reihenfolge der Elemente in s um. |
s.sort([key, reverse]) | Sortiert die Liste s. |
Nun werden die Methoden im Detail besprochen.
s.append(x)
Mit append erweitern Sie eine Liste am Ende um ein weiteres Element:
>>> s = ["Nach mir soll noch ein String stehen"]
>>> s.append("Hier ist er")
>>> s
['Nach mir soll noch ein String stehen', 'Hier ist er']
s.extend(t)
Um an eine Liste mehrere Elemente anzuhängen, verwenden Sie die Methode extend, die ein iterierbares Objekt – beispielsweise eine andere Liste – als Parameter t erwartet. Im Ergebnis werden alle Elemente von t an die Liste s angehängt:
>>> s = [1, 2, 3]
>>> s.extend([4, 5, 6])
>>> s
[1, 2, 3, 4, 5, 6]
s.insert(i, x)
Mit insert können Sie an beliebiger Stelle ein neues Element in eine Liste einfügen. Der erste Parameter i gibt den gewünschten Index des neuen Elements an, der zweite, x, das Element selbst:
>>> erst_mit_luecke = [1, 2, 3, 5, 6, 7, 8]
>>> erst_mit_luecke.insert(3, 4)
>>> erst_mit_luecke
[1, 2, 3, 4, 5, 6, 7, 8]
Ist der Index i zu klein, wird x am Anfang von s eingefügt; ist er zu groß, wird er wie bei append am Ende angehängt.
s.pop([i])
Das Gegenstück zu insert ist pop. Mit dieser Methode können Sie ein beliebiges Element anhand seines Index aus einer Liste entfernen. Ist der optionale Parameter nicht angegeben, wird das letzte Element der Liste entfernt. Das entfernte Element wird von pop zurückgegeben:
>>> s = ["H", "a", "l", "l", "o"]
>>> s.pop()
'o'
>>> s.pop(0)
'H'
>>> s
['a', 'l', 'l']
Versuchen Sie, einen ungültigen Index zu übergeben oder ein Element aus einer leeren Liste zu entfernen, wird ein IndexError erzeugt.
s.remove(x)
Möchten Sie ein Element mit einem bestimmten Wert aus einer Liste entfernen, egal, welchen Index es hat, können Sie die Methode remove bemühen. Sie entfernt das erste Element der Liste, das den gleichen Wert wie x hat.
>>> s = ["H", "u", "h", "u"]
>>> s.remove("u")
>>> s
['H', 'h', 'u']
Der Versuch, ein nicht vorhandenes Element zu entfernen, führt zu einem ValueError.
s.reverse()
Mit reverse kehren Sie die Reihenfolge der Elemente einer Liste um:
>>> s = [1, 2, 3]
>>> s.reverse()
>>> s
[3, 2, 1]
Im Unterschied zur Slice-Notation s[::-1] erfolgt die Umkehrung in-place. Es wird also keine neue list-Instanz erzeugt, sondern die alte verändert. Da dies erheblich weniger Rechenzeit und Speicher kostet, sollten Sie reverse der Slice-Notation vorziehen, wenn Sie die alte Liste nicht mehr brauchen.
s.sort([key, reverse])
Die komplexeste Methode des list-Datentyps ist sort, die eine Liste nach bestimmten Kriterien sortiert. Rufen Sie die Methode ohne Parameter auf, benutzt Python die normalen Vergleichsoperatoren zum Sortieren:
>>> l = [4, 2, 7, 3, 6, 1, 9, 5, 8]
>>> l.sort()
>>> l
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Enthält eine Liste Elemente, für die keine Ordnungsrelation definiert ist, wie zum Beispiel Instanzen des Datentyps complex, führt der Aufruf von sort ohne Parameter zu einem TypeError:
>>> lst = [5 + 13j, 1 + 4j, 6 + 2j]
>>> lst.sort()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'complex' and 'complex'
Um eine Liste nach bestimmten Kriterien zu sortieren, dient der Parameter key. Die Methode sort erwartet im Parameter key eine Funktion, die vor jedem Vergleich für beide Operanden aufgerufen wird und deshalb ihrerseits einen Parameter erwartet. Im Ergebnis werden dann nicht die Operanden direkt verglichen, sondern stattdessen die entsprechenden Rückgabewerte der übergebenen Funktion.
Im folgenden Beispiel sortieren wir eine Liste von Namen nach ihrer Länge. Zu diesem Zweck benutzen wir die Built-in Function len, die jedem Namen seine Länge zuordnet. In der Praxis sieht das dann folgendermaßen aus:
>>> l = ["Katharina", "Peter", "Jan", "Florian", "Paula", "Ben"]
>>> l.sort(key=len)
>>> l
['Jan', 'Ben', 'Peter', 'Paula', 'Florian', 'Katharina']
Immer dann, wenn der Sortieralgorithmus zwei Elemente der Liste vergleicht, beispielsweise "Florian" und "Jan", werden nicht die Elemente selbst, sondern die zugehörigen Rückgabewerte der Funktion key verglichen. In unserem Beispiel werden somit len("Florian") und len("Jan"), also die Zahlen 7 und 3, verglichen. Daher ist der String "Jan" in diesem Beispiel vor dem String "Florian" einzuordnen. Grafisch lässt sich dieses Beispiel wie in Abbildung 13.3 veranschaulichen.
Natürlich können Sie auch komplexere Funktionen als die Built-in Function len übergeben. Wie Sie Ihre eigenen Funktionen definieren, um sie beispielsweise mit sort zu verwenden, erfahren Sie in Kapitel 19, »Funktionen«.
Der letzte Parameter, reverse, erwartet für die Übergabe einen booleschen Wert, der angibt, ob die Reihenfolge der Sortierung umgekehrt werden soll:
>>> l = [4, 2, 7, 3, 6, 1, 9, 5, 8]
>>> l.sort(reverse=True)
>>> l
[9, 8, 7, 6, 5, 4, 3, 2, 1]
[»] Hinweis
Es bleibt noch anzumerken, dass sort eine Funktion ist, die ausschließlich Schlüsselwortparameter akzeptiert. Versuchen Sie trotzdem, positionsbezogene Parameter zu übergeben, führt dies zu einem Fehler. Im folgenden Beispiel versuchen wir wieder, die Namensliste nach Länge zu sortieren. Allerdings verwenden wir diesmal einen positionsbezogenen Parameter für die Übergabe von len:
>>> l = ["Katharina", "Peter", "Jan", "Florian", "Paula"]
>>> l.sort(len)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must use keyword argument for key function
Sie werden sich vielleicht fragen, wie sort mit solchen Werten verfährt, die bei der Sortierreihenfolge an der gleichen Stelle stehen. Im oben dargestellten Beispiel hatten »Jan« und »Ben« mit der Länge 3 sowie »Peter« und »Paula« mit der Länge 5 jeweils den gleichen Wert. Im nächsten Abschnitt lernen Sie, was man unter einem stabilen Sortierverfahren versteht und was das für diese Werte bedeutet.
Stabile Sortierverfahren
Eine wichtige Eigenschaft von sort ist, dass es sich um eine stabile Sortierung handelt. Stabile Sortierverfahren zeichnen sich dadurch aus, dass sie beim Sortieren die relative Position gleichwertiger Elemente nicht vertauschen. Stellen Sie sich vor, Sie hätten folgende Namensliste:
Vorname | Nachname |
---|---|
Natalie | Schmidt |
Mathias | Schwarz |
Florian | Kroll |
Ricarda | Schmidt |
Helmut | Schmidt |
Peter | Kaiser |
Nun ist es Ihre Aufgabe, diese Liste alphabetisch nach den Nachnamen zu sortieren. Gruppen mit gleichem Nachnamen sollen nach den jeweiligen Vornamen sortiert werden. Um dieses Problem zu lösen, können Sie die Liste im ersten Schritt nach den Vornamen sortieren, was zu folgender Anordnung führt:
Vorname | Nachname |
---|---|
Florian | Kroll |
Helmut | Schmidt |
Mathias | Schwarz |
Natalie | Schmidt |
Peter | Kaiser |
Ricarda | Schmidt |
Im Resultat interessieren uns jetzt nur die Positionen der drei Personen, deren Nachname »Schmidt« ist. Würden Sie einfach alle anderen Namen streichen, wären die Schmidts richtig sortiert, weil ihre relative Position durch den ersten Sortierlauf korrekt hergestellt wurde. Nun kommt die Stabilität der sort-Methode zum Tragen, weil dadurch bei einem erneuten Sortierdurchgang nach den Nachnamen diese relative Ordnung nicht zerstört wird. Das Ergebnis sähe am Ende so aus:
Vorname | Nachname |
---|---|
Peter | Kaiser |
Florian | Kroll |
Helmut | Schmidt |
Natalie | Schmidt |
Ricarda | Schmidt |
Mathias | Schwarz |
Wäre sort nicht stabil, gäbe es keine Garantie dafür, dass Helmut vor Natalie und Ricarda eingeordnet wird.
13.2.5 Weitere Eigenschaften von Listen
Im Zusammenhang mit Pythons list-Datentyp ergeben sich ein paar Besonderheiten, die nicht unmittelbar ersichtlich sind[ 43 ](Diese Besonderheiten gelten im Prinzip für alle mutablen Datentypen. ).
Sie erinnern sich sicherlich noch an die Besonderheit des Operators +=, die in Abschnitt 13.1.2 im Zusammenhang mit dem Begriff in-place erläutert wurde. Wir möchten dieses Verhalten nochmals aufgreifen und Ihnen ein paar umfassendere Erläuterungen geben.
Zum einen ist list ein veränderbarer Datentyp, und deshalb betreffen Änderungen an einer list-Instanz immer alle Referenzen, die auf sie verweisen. Betrachten wir einmal das folgende Beispiel, in dem der unveränderliche Datentyp str mit list verglichen wird:
>>> a = "Hallo "
>>> b = a
>>> b += "Welt"
>>> b
'Hallo Welt'
>>> a
'Hallo '
Dieses Beispiel erzeugt einfach eine str-Instanz mit dem Wert "Hallo " und lässt die beiden Referenzen a und b auf sie verweisen. Anschließend wird mit dem Operator += an den String, auf den b verweist, "Welt" angehängt. Wie die Ausgaben zeigen und wie wir es auch erwartet haben, wird eine neue Instanz mit dem Wert "Hallo Welt" erzeugt und b zugewiesen; a bleibt davon unberührt.
Übertragen wir dieses Beispiel auf Listen, ergibt sich ein wichtiger Unterschied:
>>> a = [1337]
>>> b = a
>>> b += [2674]
>>> b
[1337, 2674]
>>> a
[1337, 2674]
Strukturell gleicht der Code dem str-Beispiel, nur ist diesmal der verwendete Datentyp nicht str, sondern list. Der interessante Teil ist die Ausgabe am Ende, laut der a und b denselben Wert haben, obwohl die Operation nur auf b durchgeführt wurde. Tatsächlich verweisen a und b auf dieselbe Instanz, wovon Sie sich mithilfe des is-Operators überzeugen können:
>>> a is b
True
Diese sogenannten Seiteneffekte[ 44 ](Seiteneffekte werden im Zusammenhang mit Funktionen in Abschnitt 19.3.6 eine wichtige Rolle spielen. ) sollten Sie bei der Arbeit mit Listen und anderen mutablen Datentypen im Hinterkopf behalten. Wenn Sie sichergehen möchten, dass die Originalliste nicht verändert wird, legen Sie mithilfe von Slicing eine echte Kopie an:
>>> a = [1337]
>>> b = a[:]
>>> b += [2674]
>>> b
[1337, 2674]
>>> a
[1337]
In diesem Beispiel wurde die von a referenzierte Liste kopiert und so vor indirekten Manipulationen über b geschützt. Sie müssen in solchen Fällen den Ressourcenverbrauch gegen den Schutz vor Seiteneffekten abwägen, da die Kopien der Listen im Speicher erzeugt werden müssen. Das kostet insbesondere bei langen Listen Rechenzeit und Speicherplatz und kann daher das Programm ausbremsen.
Im Zusammenhang mit Seiteneffekten sind auch die Elemente einer Liste interessant: Eine Liste speichert keine Instanzen an sich, sondern nur Referenzen auf sie. Das macht Listen einerseits flexibler und performanter, andererseits aber auch anfällig für Seiteneffekte. Schauen wir uns das folgende – auf den ersten Blick merkwürdig anmutende – Beispiel an:
>>> a = [[]]
>>> a = 4 * a
>>> a
[[], [], [], []]
>>> a[0].append(10)
>>> a
[[10], [10], [10], [10]]
Zu Beginn referenziert a eine Liste, in der eine weitere, leere Liste enthalten ist. Bei der anschließenden Multiplikation mit dem Faktor 4 wird die innere leere Liste nicht kopiert, sondern nur weitere drei Male referenziert. In der Ausgabe sehen wir also viermal dieselbe Liste. Wenn man das verstanden hat, ist es offensichtlich, warum die dem ersten Element von a angehängte 10 auch den anderen drei Listen hinzugefügt wird: Es handelt sich einfach um dieselbe Liste. Abbildung 13.4 verdeutlicht diese Tatsache.
Es ist auch möglich, dass eine Liste sich selbst als Element enthält:
>>> a = []
>>> a.append(a)
Das Resultat ist eine unendlich tiefe Verschachtelung, da jede Liste wiederum sich selbst als Element enthält. Da nur Referenzen gespeichert werden müssen, verbraucht diese unendliche Verschachtelung nur sehr wenig Speicher und nicht, wie man zunächst vermuten könnte, unendlich viel. Trotzdem bergen solche Verschachtelungen die Gefahr von Endlosschleifen, wenn man die enthaltenen Daten verarbeiten möchte. Stellen Sie sich beispielsweise vor, Sie wollten eine solche Liste auf dem Bildschirm ausgeben. Das würde zu unendlich vielen öffnenden und schließenden Klammern führen. Trotzdem ist es möglich, solche Listen mit print auszugeben. Python überprüft selbstständig, ob eine Liste sich selbst enthält, und gibt dann anstelle weiterer Verschachtelungen drei Punkte (...) aus:
>>> a = []
>>> a.append(a)
>>> print(a)
[[...]]
Bitte beachten Sie, dass die Schreibweise mit den drei Punkten kein gültiger Python-Code ist, um in sich selbst verschachtelte Listen zu erzeugen.
Wenn Sie selbst mit Listen arbeiten, die rekursiv sein könnten, sollten Sie Ihre Programme mit Abfragen ausrüsten, um Verschachtelungen von Listen mit sich selbst zu erkennen, damit das Programm bei der Verarbeitung nicht in einer endlosen Schleife steckenbleiben kann.