Der Etiketten-Flughafen: Pythons Objektmodell
·
Kursüberblick
1
Das Etikett ist nicht der Koffer: Namen und Objekte
2
Versiegelte Koffer: veränderlich und unveränderlich
3
Koffer im Koffer: Container und Kopien
4
Wenn ein Koffer übergeben wird: Funktionen und Argumente
5
Zonen und Ausweise: Gültigkeitsbereiche
6
Handgepäck: Closures
7
Der Koffer-Bauplan: Klassen und Instanzen
8
Flughafen-Standardverfahren: Dunder-Methoden
9
Der Bauplan hinter dem Bauplan: Vererbung und MRO
10
Die Kontrolle: das Geheimnis des Attributzugriffs
11
Herrenlose Koffer: Speicher und GC
12
Schließfachnummern: Hash, Gleichheit und Gesamtbild
Zeit 1:40–2:30
Verlustmeldung
LOST-003
Gemeldet von
Yoon, Lehrkraft und Begleitung einer Klassenfahrt (Mittelschule)
Was fehlt
Die Ausrüstungssets für die Klassen. Das Grundset wurde „dupliziert“ und an Klasse 1 und Klasse 2 verteilt. Als Wasser in einen Koffer von Klasse 1 lief, wurden auch die Handtücher im Koffer von Klasse 2 nass.
Erledigt, wenn
Klären, wie weit die Sets beider Klassen derselbe Koffer waren, und Sets packen, die bis ganz nach innen getrennt sind.
Internationaler Flughafen Haneulseom · Gepäckzentrum
Zehn Uhr vormittags: Die Begleitlehrkraft kommt mit einem Foto nasser Handtücher ins Zentrum. Die Sets seien eindeutig mit copy() dupliziert worden, und trotzdem seien die Handtücher beider Klassen nass geworden.
Schichtleiterin Do Miran dreht ein Set um. Es gibt tatsächlich zwei äußere Koffer. Doch in den äußeren Koffern stecken keine Gegenstände, sondern Etiketten. „Hier wurden nur neue Außenkoffer gekauft. Die Etiketten darin zeigen auf dieselbe Handtuchtasche.“
Container wie Listen, Tupel, dict und set enthalten nicht die Objekte selbst. Sie enthalten Referenzen auf Objekte, also Etiketten. So wie in Lektion 1 ein Name ein Etikett war, ist auch jedes Fach einer Liste ein Etikett.
Deshalb kann dasselbe Objekt gleichzeitig in mehreren Fächern stecken.
row = [0, 0]
pair = [row, row]
row.append(7)
print(pair)
# [[0, 0, 7], [0, 0, 7]]
print(pair[0] is pair[1])
# TrueAm häufigsten richtet diese Eigenschaft bei zweidimensionalen Listen Schaden an.
grid = [[0] * 3] * 3
grid[0][0] = 9
print(grid)
# [[9, 0, 0], [9, 0, 0], [9, 0, 0]]
print(grid[0] is grid[1] is grid[2])
# True[0] * 3 erzeugt eine Liste. Das äußere * 3 kopiert diese Liste nicht dreimal, sondern steckt dreimal ein Etikett auf dieselbe Liste hinein. Ein Koffer, drei Etiketten.
Das innere [0] * 3 ist dagegen unproblematisch. Zwar zeigt jedes Fach auf dieselbe Ganzzahl 0, aber Ganzzahlen sind unveränderlich, und row[0] = 9 hängt nur das Etikett in diesem Fach auf die 9 um. Zum Problem wird das Teilen erst, wenn ein veränderliches Objekt geteilt wird.
Die Lösung ist eine Comprehension, die für jede Zeile eine neue Liste erzeugt:
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 9
print(grid)
# [[9, 0, 0], [0, 0, 0], [0, 0, 0]]Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Für das Kopieren einer Liste gibt es fünf gängige Wege. Alle liefern dasselbe Ergebnis.
import copy
kit = [["water", "map"], ["snack"]]
copies = [kit[:], list(kit), kit.copy(), copy.copy(kit)]
print([c is kit for c in copies])
# [False, False, False, False]
print([c[0] is kit[0] for c in copies])
# [True, True, True, True]Das ist eine flache Kopie (shallow copy). Der äußere Koffer wird neu angelegt, die Etiketten darin werden einfach übernommen. Deshalb macht es einen Unterschied, ob du den äußeren oder einen inneren Koffer veränderst.
a = kit[:]
a.append(["hat"]) # Fach im äußeren Koffer hinzufügen: Original bleibt gleich
print(len(kit), len(a))
# 2 3
a[0].append("towel") # inneren Koffer ändern: auch im Original sichtbar
print(kit[0])
# ['water', 'map', 'towel']Bei dict ist es genauso. d.copy(), dict(d) und {**d} sind allesamt flache Kopien.
rooms = {"class1": ["water"], "class2": ["map"]}
dup = rooms.copy()
dup["class3"] = [] # neuer Schlüssel: nicht im Original
dup["class1"].append("tea") # geteilte Liste: auch im Original sichtbar
print(rooms)
# {'class1': ['water', 'tea'], 'class2': ['map']}Probier unten =, flache Kopie und tiefe Kopie an derselben verschachtelten Liste aus. Zeile 4 verändert einen inneren Koffer, Zeile 5 fügt dem äußeren Koffer ein Fach hinzu.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Pin sagt
→ Eine flache Kopie legt nur „eine Ebene“ neu an. Stecken nur Zahlen oder Strings darin, reicht sie völlig. Stecken Listen oder dicts darin, überleg dir, ob du eine tiefe Kopie brauchst.
copy.deepcopy steigt in den Koffer im Koffer und in den Koffer darin hinab und legt alles neu an.
import copy
kit = [["water", "map"], ["snack"]]
deep = copy.deepcopy(kit)
deep[0].append("rain")
print(kit[0], deep[0], deep[0] is kit[0])
# ['water', 'map'] ['water', 'map', 'rain'] FalseEine Kopie, die bis ganz nach unten geht, hat zwei Fallen: Man trifft denselben Koffer zweimal, oder ein Koffer enthält sich selbst, und die Kopie nähme kein Ende. deepcopy löst beides mit einem Buch namens memo. Es notiert jedes bereits kopierte Objekt als {id(Original): Kopie} und verwendet beim nächsten Treffen die notierte Kopie.
x = [1]
pair = [x, x]
p2 = copy.deepcopy(pair)
print(p2[0] is p2[1], p2[0] is x)
# True False
memo = {}
copy.deepcopy(pair, memo)
print(id(x) in memo)
# TrueZwei Fächer, die im Original auf denselben Koffer zeigten, zeigen auch in der Kopie auf denselben Koffer. Sogar die Beziehungen des Teilens werden mitkopiert.
Auch eine Liste, die sich selbst enthält, führt nicht in eine Endlosschleife. Gibt man eine solche Liste aus, kürzt Python sie mit [...] ab.
a = []
a.append(a)
print(a)
# [[...]]
b = copy.deepcopy(a)
print(b[0] is b, b is a)
# True FalseEtikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
__copy__ und __deepcopy__ selbst festlegen, wie sie kopiert werden. Es genügt zu wissen, dass es das gibt.Unveränderliche Objekte ändern sich nie, also gibt es keinen Grund, eine Kopie anzulegen. copy gibt einfach das Original zurück.
import copy
t = (1, 2)
print(copy.copy(t) is t, copy.deepcopy(t) is t)
# True True
s = "gate"
print(copy.deepcopy(s) is s)
# TrueAnders sieht es aus, wenn im Tupel ein veränderliches Objekt steckt. Die flache Kopie gibt weiterhin das Original zurück. Die tiefe Kopie muss aber die Liste darin neu anlegen und erzeugt deshalb ein neues Tupel.
t2 = (1, [2])
print(copy.copy(t2) is t2)
# True
dt = copy.deepcopy(t2)
print(dt is t2, dt == t2, dt[1] is t2[1])
# False True FalseDasselbe gilt für die Strings in einer tief kopierten Liste. Die Liste ist neu, aber "map" darin ist dasselbe String-Objekt wie im Original.
kit = [["map"], ["snack"]]
deep = copy.deepcopy(kit)
print(deep[0] is kit[0], deep[0][0] is kit[0][0])
# False TrueAuch eine tiefe Kopie packt versiegelte Koffer nicht neu. Was sich nicht ändern kann, darf man gefahrlos teilen.
copy.replace(): ein neues Objekt mit einzelnen Änderungen (5 min)Will man ein unveränderliches Objekt „ändern“, erzeugt man ein neues, in dem nur einzelne Teile anders sind. Seit 3.13 gibt es dafür einen einheitlichen Weg: copy.replace(). Unterstützt wird es von Dataclasses mit frozen=True, von namedtuple, von datetime.date und ähnlichen Objekten.
import copy
from dataclasses import dataclass
from collections import namedtuple
@dataclass(frozen=True)
class Ticket:
gate: str
seat: str
tk = Ticket("A1", "12C")
tk2 = copy.replace(tk, seat="14F")
print(tk, tk2)
# Ticket(gate='A1', seat='12C') Ticket(gate='A1', seat='14F')
Pass = namedtuple("Pass", "gate seat")
print(copy.replace(Pass("A1", "12C"), gate="B7"))
# Pass(gate='B7', seat='12C')
tk.seat = "1A"
# dataclasses.FrozenInstanceError: cannot assign to field 'seat'Das Original tk bleibt, wie es ist, und es entsteht ein neues Objekt, in dem nur das geänderte Feld anders ist. Bei Objekten ohne Unterstützung (etwa Listen) kommt TypeError: replace() does not support list objects.
Sehen wir uns den Code der Begleitlehrkraft noch einmal an:
import copy
kit = [["towel", "notebook"], ["raincoat"]]
class1 = kit.copy()
class2 = kit.copy()
print(class1 is class2, class1[0] is class2[0])
# False True
class1[0][0] = "wet towel"
print(class2[0])
# ['wet towel', 'notebook']Die äußeren Koffer waren verschieden, aber das Etikett im ersten Fach zeigte auf dieselbe Handtuchtasche. Das Wasser ist in genau diese eine Tasche gelaufen. Es gibt zwei Lösungen:
kit = [["towel", "notebook"], ["raincoat"]]
class1 = copy.deepcopy(kit)
class2 = copy.deepcopy(kit)
class1[0][0] = "wet towel"
print(class2[0], class1[0] is class2[0])
# ['towel', 'notebook'] False
def new_kit():
return [["towel", "notebook"], ["raincoat"]] # bei jedem Aufruf ein neuer Koffer
print(new_kit()[0] is new_kit()[0])
# FalseEine Funktion, die das Set packt, schafft das Original zum Kopieren ganz ab. Bei jedem Aufruf wird alles von vorn gepackt, also gibt es nichts, was geteilt werden könnte.
Als die Begleitlehrkraft gegangen ist, ruft Pin ein Röntgenbild von Band 13 auf. Die beiden Geisterkoffer sehen darauf aus, als steckten sie ineinander. Die Schichtleiterin tippt eine Zeile auf den Bildschirm:
a = []
b = [a]
a.append(b)
print(a)
# [[[...]]]„Koffer, die einander enthalten. Vielleicht werden sie deshalb nicht weggeräumt, obwohl kein Etikett mehr dran hängt.“ Sie heftet das Bild in den Ordner.
Abschlussbericht — LOST-003. copy() war eine flache Kopie, beide Klassen teilten sich die innere Ausrüstungstasche. Gelöst mit copy.deepcopy oder einer Funktion, die jedes Set neu packt.
board = [[]] * 2, wenn du board[0].append("x") ausführst? Korrigiere es mit einer Comprehension.d = {"a": [1]} und e = dict(d): Sag voraus, ob e["a"].append(2) und e["b"] = [3] jeweils in d sichtbar sind.x = [1], y = [x, x], z = copy.deepcopy(y). Was ergibt z[0] is z[1]? Überleg auch, was ohne memo herausgekommen wäre.Das Notizbuch der Schichtleiterin
Container enthalten keine Objekte, sondern Referenzen (Etiketten). Dasselbe Objekt kann in mehreren Fächern stecken.
[[0] * 3] * 3 ist dreimal dieselbe Liste. Zweidimensionale Listen baut man mit einer Comprehension.
Flache Kopie (lst[:], list(), .copy(), copy.copy): nur außen neu, innen geteilt.
Tiefe Kopie (copy.deepcopy): alles bis ganz innen neu. memo kopiert Geteiltes und Zyklen nur einmal.
Unveränderliche Objekte bleiben beim Kopieren dasselbe Objekt. Steckt Veränderliches darin, legt nur deepcopy neu an.
copy.replace(): neues Objekt, in dem nur einzelne Teile eines unveränderlichen Objekts anders sind (3.13).
← Zurück
Lektion 2 · Versiegelte Koffer: veränderlich und unveränderlich
Weiter →
Lektion 4 · Wenn ein Koffer übergeben wird: Funktionen und Argumente
Über neue Lektionen informiert werden
Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.
R
ReflexKR
Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.
ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee
Hinweis zu Affiliate-Links© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Fragen & Antworten
Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.