R

ReflexKR

Reflex-Dokumentation

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

Der Etiketten-Flughafen: Pythons Objektmodell
Lektion 2

Zeit 0:50–1:40

Versiegelte Koffer: veränderlich und unveränderlich

Verlustmeldung

LOST-002

Der versiegelte Koffer ist voller geworden

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.“

2-1. Versiegelte und offene Koffer (9 min)

Objekte gibt es in zwei Sorten. Kann man ihren Inhalt nach dem Erzeugen noch ändern, sind sie veränderlich (mutable), sonst unveränderlich (immutable).

SorteTypische TypenAm Flughafen wäre das
veränderlichlist, dict, setein Koffer mit Reißverschluss. Man kann etwas hinein- und herausnehmen, und es bleibt derselbe Koffer.
unveränderlichint, float, str, tuple, frozenset, bool, Noneein 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 assignment
fs = 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)

Nach Zeile 3 bleibt die Ganzzahl 10 bestehen. Es entsteht ein neuer Koffer 11, und nur das Etikett n wandert dorthin. m zeigt weiterhin auf 10.

2-2. Die zwei Gesichter von += (10 min)

a += b sieht nach einer einzigen Operation aus. Python geht aber so vor:

  1. Hat der Typ von 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.
  2. Gibt es sie nicht, wird wie bei 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 False

Listen 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
TypWas a += x tutSichtbar über ein anderes Etikett b?
list__iadd__: extend am selben Kofferja
tuple, str, int__add__: neues Objekt erzeugen, nur a umhängennein

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)

Bei list zeigen auch nach Zeile 3 beide Pfeile auf denselben Koffer. Bei tuple und str entsteht ein neuer Koffer, und nur a wandert dorthin. Die 1 und 2 im neuen Tupel sind dieselben Ganzzahlobjekte wie im ursprünglichen Tupel.

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).

2-3. Versiegelt ist nur der äußere Koffer: die Liste im Tupel (8 min)

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 assignment

Der 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 assignment

In 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)

Im letzten Schritt wird ein Fehler angezeigt, doch in der Liste im Bild steckt schon die 3. Das Tupelfach zeigt nach wie vor auf dieselbe Liste.

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'

2-4. Was Strings zusammenfügen mit += 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))
# True

Hä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äufeplusplus_sharedjoin
100.000ca. 6 msca. 120 msca. 3 ms
200.000ca. 12 msca. 460 msca. 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.

2-5. Kleine Ganzzahlen und Interning: warum man Werte nicht mit 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 False

Die 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 "=="?
# True

String-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)
# True

Weil 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 ==.

Fall abgeschlossen (10 min)

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',))
  • Der erste Koffer war nur außen versiegelt. Das __iadd__ der inneren Liste hat zuerst geklappt, nur die Zuweisung ans Fach ist gescheitert.
  • Beim zweiten Koffer ist auch das Innere ein Tupel. Tupel haben kein __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.

Übungen

  1. Was ist y, nachdem x = 1000, y = x und x += 1 gelaufen sind? Zeichne die Etiketten, dann prüfe es.
  2. Was passiert mit t, wenn du s = {1}, t = s und s |= {2} ausführst? Probier es auch mit frozenset({1}).
  3. Sei 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.

Erhoben: deine E-Mail-Adresse · Zweck: Hinweise auf neue Lektionen oder den Start dieses Kurses · Speicherdauer: bis du die Löschung verlangst. Keine andere Nutzung, keine Weitergabe. Antworte einfach auf eine Benachrichtigung, dann löschen wir sie sofort.

Fragen & Antworten

Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.

Wird als reiner Text angezeigt, ohne Links oder Formatierung.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex