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 0:50–1:40
Verlustmeldung
LOST-002
Gemeldet von
Oh, Bestandsverwaltung im Duty-free-Shop
Was fehlt
Ein Koffer mit Siegelaufkleber (ein Tupel). Die Waage meldete den Fehler „Versiegelte Koffer lassen sich nicht öffnen“, doch beim erneuten Nachsehen lag ein Gegenstand mehr darin.
Erledigt, wenn
Klären, warum trotz Fehler etwas hineingekommen ist, und einen Koffer packen, der sich wirklich nicht öffnen lässt.
Internationaler Flughafen Haneulseom · Gepäckzentrum
Bei der Morgenbesprechung hält Schichtleiterin Do Miran einen Siegelaufkleber hoch. „Einen Koffer mit diesem Aufkleber darf niemand öffnen. Die Meldung von heute sagt aber, dass ein versiegelter Koffer voller geworden ist.“
Der Code aus der Bestandsverwaltung des Duty-free-Shops ist kurz. Beim Versuch, etwas in ein Tupel zu legen, kam ein TypeError. Als man das Tupel danach ausgab, war der Gegenstand trotzdem darin. Die Schichtleiterin sagt: „Das Siegel klebt am äußeren Koffer. Den Koffer darin hat niemand versiegelt.“
Objekte gibt es in zwei Sorten. Kann man ihren Inhalt nach dem Erzeugen noch ändern, sind sie veränderlich (mutable), sonst unveränderlich (immutable).
| Sorte | Typische Typen | Am Flughafen wäre das |
|---|---|---|
| veränderlich | list, dict, set | ein Koffer mit Reißverschluss. Man kann etwas hinein- und herausnehmen, und es bleibt derselbe Koffer. |
| unveränderlich | int, float, str, tuple, frozenset, bool, None | ein versiegelter Koffer. Wer den Inhalt ändern will, muss einen neuen Koffer packen. |
Unveränderliche Objekte bieten überhaupt keine Möglichkeit, ihren Inhalt zu ändern.
word = "gate"
print(word.upper(), word)
# GATE gate
word[0] = "G"
# TypeError: 'str' object does not support item assignmentfs = frozenset({1, 2})
fs.add(3)
# AttributeError: 'frozenset' object has no attribute 'add'word.upper() ändert word nicht. Es erzeugt einen neuen String in Großbuchstaben und gibt ihn zurück. Alle String-Methoden arbeiten so.
Was ist dann n = n + 1? Hier wird nicht der Koffer mit der Ganzzahl 10 umgebaut. Python besorgt ein neues Ganzzahlobjekt 11 und hängt das Etikett n dorthin um. Eine „Änderung“ an einem unveränderlichen Objekt ist immer ein neues Objekt plus Neubindung.
Die Etikettentafel unten zeichnet auch Ganzzahlen als eigene Koffer. Achte darauf, wohin der Pfeil von n nach Zeile 3 zeigt.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
+= (10 min)a += b sieht nach einer einzigen Operation aus. Python geht aber so vor:
a eine Methode __iadd__ (Addition an Ort und Stelle), wird sie aufgerufen. Sie verändert den Koffer an Ort und Stelle und gibt denselben Koffer zurück.a = a + b mit __add__ ein neues Objekt erzeugt und das Etikett umgehängt.print(hasattr(list, "__iadd__"), hasattr(int, "__iadd__"))
# True False
print(hasattr(str, "__iadd__"), hasattr(tuple, "__iadd__"))
# False FalseListen sind veränderlich und haben deshalb __iadd__. Unveränderliche Typen haben es nicht, denn einen versiegelten Koffer kann man nicht an Ort und Stelle umbauen. Darum führt dasselbe += zu unterschiedlichen Ergebnissen:
a = [1, 2]
b = a
a += [3]
print(a, b, a is b)
# [1, 2, 3] [1, 2, 3] True
a = (1, 2)
b = a
a += (3,)
print(a, b, a is b)
# (1, 2, 3) (1, 2) False
a = "T1"
b = a
a += "-A"
print(a, b, a is b)
# T1-A T1 False| Typ | Was a += x tut | Sichtbar über ein anderes Etikett b? |
|---|---|---|
list | __iadd__: extend am selben Koffer | ja |
tuple, str, int | __add__: neues Objekt erzeugen, nur a umhängen | nein |
Das ist der Grund, warum a = a + [3] aus Lektion 1 und a += [3] bei einer Liste verschiedene Ergebnisse liefern: Ersteres erzeugt eine neue Liste, Letzteres behält dieselbe. Auch |= bei set und bei dict ändert wie bei Listen an Ort und Stelle.
Probier die drei Typen abwechselnd aus.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Pin sagt
→ Bekommt eine Funktion eine Liste und macht damit x += [...], ändert sich auch die Liste beim Aufrufer. Bei einer Zahl oder einem String bliebe draußen alles beim Alten. Merk dir: Dieselbe Zeile verhält sich je nach Typ anders (Lektion 4).
Dass ein Tupel unveränderlich ist, heißt genau: Die Referenzen, die es enthält, lassen sich nicht ändern. Auf welches Objekt jedes Fach zeigt, steht fest. Ist dieses Objekt aber eine Liste, kann sich die Liste selbst beliebig ändern.
t = (1, [2])
t[1].append(3)
print(t)
# (1, [2, 3])
t[1] = [9]
# TypeError: 'tuple' object does not support item assignmentDer Code der Meldung benutzt statt append ein +=. Dann passiert etwas Seltsames:
t = (1, [2])
try:
t[1] += [3]
except TypeError as e:
print("TypeError:", e)
print(t)
# TypeError: 'tuple' object does not support item assignment
# (1, [2, 3])Es gab einen Fehler, und trotzdem hat sich die Liste geändert. Ausgeschrieben besteht t[1] += [3] nämlich aus zwei Schritten:
t = (1, [2])
tmp = t[1]
tmp = tmp.__iadd__([3]) # Schritt 1: Liste an Ort und Stelle ändern (klappt)
print(t)
# (1, [2, 3])
t[1] = tmp # Schritt 2: wieder ins Tupelfach legen (scheitert)
# TypeError: 'tuple' object does not support item assignmentIn Schritt 1 ist die Liste bereits geändert. Die Zuweisung ans Fach in Schritt 2 scheitert und löst den Fehler aus, aber rückgängig machen lässt sich nichts mehr. Deshalb hat die Waage „Versiegelte Koffer lassen sich nicht öffnen“ gerufen und den Gegenstand trotzdem hineingelegt.
Etikettentafel
Schritt 1 / 0
Als Nächstes läuft: 0
Labor wird vorbereitet…
Gerade geändert
Versiegelter Koffer (unveränderlich)
Ob auch das Innere versiegelt ist, lässt sich leicht prüfen: Funktioniert hash()? Ein Tupel mit Hashwert enthält nur Objekte, die ebenfalls hashbar (meist unveränderlich) sind (Lektion 12).
print(hash(("passport", ("snack",))) == hash(("passport", ("snack",))))
# True
hash(("passport", ["snack"]))
# TypeError: unhashable type: 'list'+= kostet (6 min)Strings sind unveränderlich, also erzeugt s += "x" im Prinzip jedes Mal einen neuen String. Fügt man einen String der Länge n Zeichen für Zeichen zusammen, wächst die Zahl der kopierten Zeichen auf 1 + 2 + … + n.
def plus(n):
s = ""
for i in range(n):
s += "x"
return s
def plus_shared(n):
s = ""
for i in range(n):
last = s # am selben String hängt noch ein weiteres Etikett
s += "x"
return s
def join(n):
return "".join(["x" for i in range(n)])
print(plus(5) == plus_shared(5) == join(5))
# TrueHängt an einem String nur ein einziges Etikett, verlängert CPython ihn bei += heimlich an Ort und Stelle. Deshalb ist plus schneller als gedacht. Kommt aber auch nur ein Etikett hinzu, fällt diese Optimierung weg. Gemessen auf diesem Rechner (CPython 3.14.2):
| Durchläufe | plus | plus_shared | join |
|---|---|---|---|
| 100.000 | ca. 6 ms | ca. 120 ms | ca. 3 ms |
| 200.000 | ca. 12 ms | ca. 460 ms | ca. 5 ms |
Bei plus_shared hat sich die Zeit fast vervierfacht, als sich die Durchläufe verdoppelt haben. So sieht es aus, wenn die Kopierarbeit quadratisch wächst. Die Optimierung garantiert die Sprache nicht. Wer in einer Schleife Strings sammelt, legt sie deshalb in eine Liste und fügt sie am Ende mit "".join() in einem Rutsch zusammen.
is vergleicht (7 min)Unveränderliche Objekte ändern sich nie. Deshalb ist es ungefährlich, wenn sich viele Stellen bei gleichem Wert ein einziges Objekt teilen. CPython nutzt das und legt häufig gebrauchte Objekte im Voraus an.
Cache für kleine Ganzzahlen. CPython erzeugt die Ganzzahlen von -5 bis 256 beim Start und gibt immer dieselben Objekte zurück.
print(int("256") is int("256"), int("257") is int("257"))
# True False
print(int("-5") is int("-5"), int("-6") is int("-6"))
# True FalseDie Zahlen werden hier absichtlich zur Laufzeit mit int("…") erzeugt. Schreibt man Literale direkt in den Code, etwa 257 is 257, kann der Compiler gleiche Konstanten zu einer zusammenfassen, und es kommt True heraus. Python warnt sogar vor solchen Vergleichen:
x = 256
print(x is 256)
# SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
# TrueString-Interning. CPython fasst String-Konstanten, die wie Namen aussehen, zu einem einzigen Objekt zusammen (Interning). Zur Laufzeit erzeugte Strings können trotz gleichen Werts verschiedene Objekte sein. Mit sys.intern() bringst du gleiche Strings auf ein gemeinsames Objekt.
import sys
a = "gate"
b = "".join(["ga", "te"])
print(a == b, a is b)
# True False
print(sys.intern(b) is a)
# TrueWeil das Ergebnis von Cache und Interning abhängt, stimmt ein Wertevergleich mit is bei manchen Werten und bei anderen nicht. Werte vergleichst du immer mit ==, und is verwendest du nur für Objekte, die es genau einmal gibt, wie None.
Pin sagt
→ Wer im Test nur mit kleinen Zahlen prüft, bekommt bei x is 100 immer True, und der Fehler bleibt versteckt. Sobald im Betrieb 1000 hereinkommt, ist es False. Zahlen und Strings vergleichst du mit ==.
Hier der Code aus der Meldung und wie man einen Koffer packt, der sich wirklich nicht öffnen lässt:
sealed = ("passport", ["snack"])
try:
sealed[1] += ["perfume"]
except TypeError as e:
print("scale:", e)
print(sealed)
# scale: 'tuple' object does not support item assignment
# ('passport', ['snack', 'perfume'])
sealed = ("passport", ("snack",)) # auch innen ein Tupel
try:
sealed[1] += ("perfume",)
except TypeError as e:
print("scale:", e)
print(sealed)
# scale: 'tuple' object does not support item assignment
# ('passport', ('snack',))__iadd__ der inneren Liste hat zuerst geklappt, nur die Zuweisung ans Fach ist gescheitert.__iadd__, also wird lediglich ein neues Tupel erzeugt, und die Zuweisung ans Fach scheitert. Der ursprüngliche Koffer bleibt unverändert.Die Schichtleiterin klebt auch auf den inneren Koffer ein neues Siegel. „Ein Siegel hört nicht außen auf. Du musst dir auch die Koffer darin ansehen.“
Abschlussbericht — LOST-002. Das Tupel hielt nur seine Referenzen fest, die Liste darin war veränderlich. t[1] += [...] erweiterte die Liste an Ort und Stelle und scheiterte erst bei der Zuweisung ans Fach. Gelöst, indem auch das Innere unveränderlich (ein Tupel) wurde.
y, nachdem x = 1000, y = x und x += 1 gelaufen sind? Zeichne die Etiketten, dann prüfe es.t, wenn du s = {1}, t = s und s |= {2} ausführst? Probier es auch mit frozenset({1}).t = ([],). Wie enden t[0] += [1] und t[0].extend([1]) jeweils? Gib danach auch t aus.Das Notizbuch der Schichtleiterin
Veränderlich: list, dict, set. Unveränderlich: int, float, str, tuple, frozenset.
Eine „Änderung“ an einem unveränderlichen Objekt erzeugt immer ein neues Objekt und hängt das Etikett um.
+= arbeitet an Ort und Stelle, wenn es __iadd__ gibt (list), sonst entsteht ein neues Objekt (int, str, tuple).
Ein Tupel hält nur seine Referenzen fest. Die Liste darin ändert sich. t[1] += [...] wirft einen Fehler und ändert trotzdem.
Strings in einer Schleife sammelt man in einer Liste und verbindet sie mit "".join().
Cache für kleine Ganzzahlen und Interning sind CPython-Details. Werte vergleicht man immer mit ==.
← Zurück
Lektion 1 · Das Etikett ist nicht der Koffer: Namen und Objekte
Weiter →
Lektion 3 · Koffer im Koffer: Container und Kopien
Ü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.