In dieser Lektion werden wir mehr ins Detail gehen bezüglich der Frage, wie Python Listen speichert. Listen zu kopieren und zu vergleichen kann unerwartete Konsequenzen haben. Wir werden erklären, wie Python intern funktioniert, so dass du diese Fehler verstehen und vermeiden kannst. Das Problem im Allgemeinen ist, dass mehrere unterschiedliche Variablen auf dieselbe Liste "zeigen" oder dieselbe Liste "bezeichnen" können. Gegen Ende der Lektion beschreiben wir den "is
" Operator, der uns sagt, ob zwei Variablen wirklich auf genau dieselbe Liste verweisen.
Beispiel
Nehmen wir zum Beispiel ein Code Fragment, das eine Liste von Längen in Zoll mit dem Namen oldSize
in eine Liste derselben Längen in Zentimetern mit dem Namen newSize
konvertiert. Der naheliegende Weg, dies zu erreichen, ist, die Zeile newSize = oldSize
zu benutzen, um eine Kopie zu erzeugen, und sie dann durchzugehen, um alle Werte zu ändern:
oldSize = ["letter", 8.5, 11] # Größe eines Papiers in Zoll newSize = oldSize # erstelle eine Kopie newSize[1] = newSize[1]*2.54 # konvertiere in cm newSize[2] = newSize[2]*2.54 # konvertiere in cmJetzt lasst uns sehen, ob das tatsächlich funktioniert. Wenn man
newSize
ausgibt, bekommt man ["letter", 21.59, 27.94]
wie erwartet. Aber es gibt eine große Überraschung, wenn man auch oldSize
ausgibt: die Werte von oldSize
haben sich verändert! Zur Illustration:
Wir werden nun im Detail erklären, was genau passiert ist, mit Hilfe einiger Diagramme. Das Hauptproblem ist, dass newSize = oldSize
nicht wirklich die vollständige Liste kopiert hat: es hat nur einen Verweis (arrow) auf dieselbe Liste kopiert.
Klicke auf die Folientitel, um den Tab zu wechseln.
Wir werden eine Tabelle benutzen, um die Variablen und Werte in Pythons Speicher zu repräsentieren. Zum Beispiel, nachdem wir folgenden Code laufen lassen
city = "Moose Factory" population = 2458 employer = cityzeigt diese Tabelle (mit dem schwarzen Rand) wie Pythons Speicher aussieht:
Der Name der ersten Variable ist city
und ihr Wert ist der String "Moose Factory"
. Der Name der zweiten Variable ist population
und ihr Wert ist die Zahl 2458
. Der Name der dritten Variable ist employer
und ihr Wert ist der String "Moose Factory"
.
Als nächstes zeigen wir, wie eine Liste im Speicher aussieht. Nehmen wir zum Beispiel das Code Fragment
myList = ["Moose Factory", 2458]Dies wird nur eine Variable mit dem Namen
myList
erstellen. Eine Liste wird erstellt und der Wert von myList
wird gesetzt als Äquivalent zu "zeigen" oder "verweisen" auf diese Liste. Wir repräsentieren die Liste mit Hilfe einer Box, und die Werte der Listeneinträge werden in der Box neben den ihnen entsprechenden Indizes angezeigt. Die Liste wird in blau angezeigt.Der Pfeil zeigt, dass myList
auf diese neue Liste verweist. Das Element bei Index 0 auf der Liste ist der String "Moose Factory"
, und das Element bei Index 1 ist die Zahl 2458
. Zum Beispiel, wenn Du print(myList[1])
schreibst, wird Python 2458
ausgeben.
Jetzt fügen wir zu Illustrationszwecken eine weitere Zeile zum vorigen Beispiel hinzu. Ein Baby wird geboren, also lassen wir folgendes Codefragment laufen
myList = ["Moose Factory", 2458] myList[1] = myList[1]+1Python berechnet
2458+1
was äquivalent zu 2459
ist, und dies ersetzt nun den Wert bei Index 1 auf der Liste. Nach dem Update haben wir das unten gezeigte Diagramm.(Die 2458 ist kein Teil von Pythons Speicher, sondern ist nur da um die Veränderung hervorzuheben.)
oldSize
und newSize
oldSize = ["letter", 8.5, 11]also sieht Pythons Speicher wie das Diagramm unten aus, nachdem diese Zeile ausgeführt wurde. Wir haben eine Liste der Länge 3 erstellt.
newSize = oldSizeund hier finden wir tatsächlich das Hauptproblem:
=
dupliziert die Liste nicht! Stattdessen hat es nur einen neuen Verweis auf dieselbe Liste kopiert. Dies wird illustriert von zwei unterschiedlichen Pfeilen, die auf genau dieselbe Box zeigen. Das ist ein ziemlicher Unterschied dazu, Zahlen oder Ausdrücke zu kopieren (wie auf der ersten Folie).
Wie das Diagramm zeigt, haben wir zwei Variablen, die beide auf dieselbe Liste verweisen.
newSize[1] = newSize[1]*2.54Python sieht
newSize
, schlägt den Wert von dessen Index 1 nach (8.5) und multipliziert ihn mit 2.54, dann ersetzt es den Wert wie gezeigt. Aber, da oldSize
sich auf dieselbe Liste bezieht, ist ein Nebeneffekt, dass wir auch oldSize
modifiziert haben!(Wiederum ist 8.5 nicht Teil von Pythons Speicher, sondern wird benutzt, um die Veränderung hervorzuheben.)
newSize[2] = newSize[2]*2.54was auch den anderen Wert in der Liste beeinflusst. Nachdem diese Zeile ausgeführt ist, sieht Pythons Speicher aus wie das Diagramm unten.
Wenn wir nun entweder newSize
oder oldSize
ausgeben, gibt uns Python ["letter", 21.59, 27.94]
.
Ein anderer Weg sich dieses Beispiel anzusehen, ist, es durch ein Visualization Tool laufen zu lassen. Falls Du das tust, musst Du beachten, dass Listen, anstatt von Pfeilen, mittels ihrer "ID" dargestellt werden. Jede Liste lebt gewissermaßen an einer "Adresse", die äquivalent zu ihrer ID ist; ein Adresswert ist wie ein Pfeil, der auf eine Box an einer ID Adresse zeigt. |
Wie man in Python eine Liste richtig kopiert
Obwohl es manchmal nützlich ist, mehrere Verweise auf die selbe Liste zu haben, ist es hier nicht das, was wir wollen.
Wir geben drei Lösungen. Sie sind alle mehr oder weniger äquivalent, aber jede wird dir etwas Neues über Python beibringen, also ist es die Zeit wert, alle drei zu lesen.
Methode 1, Verwendung von [:]
Oben demonstrieren wir, dass newList = oldList[:]
eine echte Kopie der alten Liste erstellt. Obwohl diese Syntax seltsam aussieht, ist sie verwandt mit etwas, was wir bereits kennengelernt haben. In der Lektion zu Strings haben wir ein Verfahren vorgestellt, um Teilstrings zu isolieren: string[first:tail]
gibt uns den Teilstring, der mit dem Index first
anfängt und mit dem Index tail-1
endet. Wir hatten erwähnt, dass es auch verwendet werden kann, um Teillisten von Listen zu erstellen. Zusätzlich,
- wenn Du
first
auslässt, nimmt es den Defaultwert von0
; - wenn Du
tail
auslässt, nimmt es den Defaultwert vonlen
(die Länge der Liste/des Strings).
Was also tatsächlich passiert, ist dass oldList[:]
eine neue Teilliste erstellt, allerdings eine, die dieselben Daten enthält wie die Originalliste, also ist es eine neue Kopie.
Methode 2, Verwendung von copy.copy()
Es gibt ein Modul namens copy
, das mehrere Methoden in Verbindung mit dem Kopieren enthält. Die einfachste ist copy.copy(L)
: wenn sie auf eine Liste L
angewandt wird, gibt die Funktion eine echte Kopie von L
aus.
Die copy.copy()
Funktion kann auch für andere Arten von Objekten verwendet werden, aber diese werden hier nicht diskutiert werden.
Methode 3, Verwendung von list()
Die letzte Weise, eine echte Kopie zu erstellen, ist die Verwendung der list() Funktion. Normalerweise wird list()
benutzt, um Daten von anderen Typen in den list
Typen zu konvertieren. (Zum Beispiel list("hello")
konvertiert den String "hello" in eine Liste mit fünf Elementen, jedes davon ein Buchstabe.) Aber wenn Du versuchst, etwas, das bereits eine Liste ist, in eine Liste zu konvertieren, erstellt es einfach eine Kopie.
Listen als Argumente
Beachte, dass aufgrund der Art und Weise wie eine Liste funktioniert, jede Funktion, bei der eine Liste als Argument funktioniert, auch tatsächlich die Inhalte einer Liste verändern kann. (Du hast dies schon in der replace Übung gesehen.)
def func(list): list[0] = list[0] + list[1] list[1] = list[1] + list[0] data = [3, 4] func(data) print(data[0]*data[1])
func
ändern wir das Element beim Index 0 auf 7, und den Wert beim Index 1 auf 7+4=11. Also haben wir das Ergebnis 7*11.Listen mit Hilfe von is
vergleichen
Wann sind zwei Listenvariablen L1
und L2
äquivalent? Es gibt zwei unterschiedliche Möglichkeiten, diese Frage zu interpretieren:
- Selbe Identität: Zeigen/Verweisen
L1
undL2
auf genau dasselbe Listenobjekt? - Selbe Werte: Sind die Inhalte von Liste
L1
äquivalent zu den Inhalten von ListeL2
?
Es stellt sich heraus, dass in Python der Standard Äquivalenz- oder auch Gleichheitsoperator ==
die Selbe Werte Bedeutung hat, wie das folgende Beispiel zeigt.
is
Operator. Wir benutzen diesen Operator genauso wie ==
: die Syntax
«list1» is «list2»
gibt True
aus, falls die Listen auf dieselbe Liste verweisen und False
, falls sie auf unterschiedliche Listen Verweisen (sogar wenn diese dieselben Inhalte haben).
True
im Output dieses Programms vor? (Zeichne ein Diagramm, um den Überblick zu behalten.)
list1 = [9, 1, 1] list3 = list(list1) list2 = list1 list4 = list3[:] list1[0] = 4 list5 = list1 print(list1 is list2, list2 is list3, list3 is list4, list4 is list5) print(list1 == list2, list2 == list3, list3 == list4, list4 == list5)
True False False False
Du solltest is nicht mit Strings oder Zahlen benutzen, weil == bereits in korrekter Weise Äquivalenz testet, und das Verhalten von is ist schwer vorherzusagen in Bezug auf Strings und Zahlen. |
Verschachtelte lists
Wir haben bereits die meisten wichtigen Informationen erwähnt, aber es gibt noch eine weitere häufige Situation, die es zu erwähnen lohnt. Eine geschachtelte Liste ist eine Liste innerhalb einer anderen Liste, zum Beispiel
sample = [365.25, ["first", 5]]
zeigt eine geschachtelte Liste. Die äußere Liste, auf die
sample
verweist, hat zwei Elemente; das Element an Index 0 ist eine Gleitpunktzahl ("float") und das Element an Index 1 ist die innere Liste. Die innere Liste ist ["first", 5]
. (Du kannst auch eine Verschachtelung mit mehreren Ebenen herstellen.) Sobald Du anfängst, verschachtelte Listen zu benutzen, denk daran:
- Die drei Methoden von oben auf
sample
anzuwenden, kopiert die äußere Liste, aber nicht die innere Liste. Alsocopy(sample)[1] is sample[1]
, was bedeutet, dass die Kopie immernoch einen Verweis auf einen Teil der ursprünglichen Liste enthält. Das war vermutlich nicht was Du wolltest. Wenn Du eine echte Kopie auf jeder Ebene machen willst, benutzecopy.deepcopy()
. - Geschachtelte Listen mit
==
zu testen ist relativ intuitiv: Python wendet==
rekursiv auf jedes Listenelement an. Zum Beispiel[[1, 2], 3]==[[1, 2], 3]
istTrue
, und[[1, 2], 3]==[1, 2, 3]
istFalse
, da die ersten Elemente unterschiedlich sind ([1, 2] != 1
).
Du hast diese Lektion abgeschlossen! Das folgende Extramaterial ist freiwillig.
Tupel: ("immutable", "lists")
Wir haben oben erwähnt, dass, wenn Du eine Funktion auf eine Liste anwendest, sie die Liste verändern kann. Manchmal willst Du das unmöglich machen! Eine Lösung in Python ist es, Tupel zu erstellen, die dasselbe wie Listen sind, außer, dass sie nicht verändert werden können. Wir sagen Listen sind "veränderlich" und Tupel sind "unveränderlich". (Strings und Zahlen sind auch unveränderlich.) Das kann eine nützliche Strategie sein, um zu verhindern, dass Programmierfehler Listen verändern, die nicht geändert werden sollten. Tupel sind beinahe identisch mit Listen, außer dass sie runde Klammern benutzen ()
anstatt eckiger Klammern []
. Du kannst zwischen Tupeln und Listen konvertieren, indem Du tuple()
und list()
benutzt.
Zur ∞
und darüber hinaus: Selbst-Enthaltung
Es ist möglich, eine Liste zu haben, die sich selbst enthält! Mache einfach eine Liste und dann lasse eines ihrer Elemente auf die ganze Liste zurück verweisen:
Du siehst, dass Pythons Outputgenerator schlau genug ist, zu erkennen, dass die Liste in einer Schleife auf sich selbst zurück verweist: er gibt "...
" aus anstatt L
noch einmal als Ganzes auszugeben, um eine unendliche Schleife zu vermeiden.