Referenzen
Einführung
Eingangs wurde erklärt, dass man sich Variablen wie einen kleinen Kasten vorstellen kann. Bisher genügte uns diese vereinfachte Vorstellung. Um einige Effekte zu verstehen, müssen wir nun aber genauer werden.
In Python stellen Variablen immer eine Referenz auf ein Objekt dar. Dies können wir uns so vorstellen, dass eine Variable im Grunde ein Pfeil auf einen gespeicherten Wert oder besser auf den Speicherort dieses Wertes ist. Veranschaulichen wir dies mit einem kleinen Beispiel:
erste_zahl = 2
zweite_zahl = erste_zahl
Mit der ersten Anweisung wir eine Variable mit dem Wert 2 erstellt, d.h., irgendwo im Arbeitsspeicher liegt nun dieser Wert. Mit erste_zahl
finden wir den Ort im Arbeitsspeicher und sehen, dass dort der Wert 2 liegt. Die zweite Anweisung hat nicht den Effekt, dass die 2 ein weiteres Mal im Speicher abgelegt wird. Stattdessen haben wir nun einen zweiten Pfeil, der auf denselben Ort zeigt.
Was geschieht, wenn wir anschließend noch die Anweisung zweite_zahl = 1
geben? Dies verändert natürlich den unter zweite_zahl
referenzierten Wert, aber da beide Variablen auf denselben Ort verweisen, könnte es doch sein, dass auch erste_zahl
verändert wird. Dies ist in Python jedoch nicht der Fall. Der Grund ist, dass ein int-Wert in Wahrheit ein sogenannten Objekt vom Typ int ist und Objekte in Python unveränderlich sind. Das bedeutet, der gespeicherte Wert 2 kann nicht verändert werden, stattdessen wird ein neues Objekt mit dem neuen Wert erstellt. Auf dieses neue Objekt zeigt dann zweite_zahl
:
Solange wir mit einfachen Datentypen wie int oder boolean arbeiten würde uns dies gar nicht auffallen. Erst bei der Verwendung von komplizierten Typen wird es wichtig, dass wir diese Vorstellung verwenden.
Listen speichern Referenzen
Sehen wir uns dieses Beispiel an:
erste_liste = [2, 3, 5, 7]
Hier wird eine Liste mit vier int-Werten erstellt. In Wahrheit speichert diese Liste nicht unmittelbar diese Werte, sondern die Referenzen auf diese Werte:
Fügen wir nun noch die Anweisung zweite_Liste = erste_liste
hinzufügen haben wir zwei Referenzen auf unsere Liste und können entsprechend die gespeicherten Werte unter zwei Namen erreichen:
Dass wir die Werte auf zwei Wegen erreichen können, hat einen Effekt, den man kennen sollte, da er sonst zu Verwirrung führen könnte: Eine Änderung an den Werten einer der Listen hat unmittelbar dieselbe Wirkung auf die andere. Ergänzen wir also zum Beispiel die Anweisung erste_liste[0]=1
, so beginnen beiden Listen mit dem Wert 1.
Möchten wir eine zweite Liste mit denselben Werten wir eine bereits vorhandene Liste erstellen, in der wir aber diese Werte unabhängig von der ursprünglichen Liste verändern können, benötigen wir die copy
-Methode:
erste_liste = [2, 3, 5, 7]
zweite_liste = erste_liste.copy()
Dies führt zu einer zweiten Liste mit neuen Referenzen auf dieselben Werte.
Ergänzen wir nun die Anweisung zweite_liste[0] = 1
wird tatsächlich nur in der zweiten Liste der erste Eintrag geändert.
Diese zweite Liste ist eine sogenannte flache Kopie der ersten. Der Name erklärt sich dadurch, dass bei einer tieferen Verschachtelung wieder derselbe Effekt wie weiter oben auftritt. Sehen wir uns ein Beispiel an:
erste_liste = [[2,4,6], [3,6,9]]
zweite_liste = erste_liste.copy()
erste_liste[0] = 12
print(erste_liste) # Ausgabe: [12, [3, 6, 9]]
print(zweite_liste) # Ausgabe: [[2, 4, 6], [3, 6, 9]]
erste_liste[1][2] = 12
print(erste_liste) # Ausgabe: [12, [3, 6, 12]]
print(zweite_liste) # Ausgabe: [[2, 4, 6], [3, 6, 12]]
Wir sehen, dass die Anweisung erste_liste[0] = 12
nur den ersten Eintrag der ersten Liste verändert. Die Anweisung erste_liste[1][2] = 12
, die in einer tieferen Ebene einen Eintrag verändert, führt jedoch zu einer Veränderung beider Listen.
Möchten wir eine komplett unabhängige Kopie einer Liste in der auch die inneren Listen kopiert werden, müssen wir mit einer tiefen Kopie arbeiten:
from copy import deepcopy
erste_liste = [[2,4,6], [3,6,9]]
zweite_liste = deepcopy(erste_liste)
erste_liste[0] = 12
print(erste_liste) # Ausgabe: [12, [3, 6, 9]]
print(zweite_liste) # Ausgabe: [[2, 4, 6], [3, 6, 9]]
erste_liste[1][2] = 12
print(erste_liste) # Ausgabe: [12, [3, 6, 12]]
print(zweite_liste) # Ausgabe: [[2, 4, 6], [3, 6, 9]]
Hier hat nun wirklich keine Veränderung der ersten Liste mehr Einfluss auf die zweite, da auch die inneren Listen kopiert wurden.