mutable and immutable in python

mutable and immutable in python

Es war drei Uhr morgens, als das Telefon klingelte. Ein E-Commerce-System, das wir für einen Kunden in München gebaut hatten, spuckte plötzlich falsche Preise für Warenkörbe aus. Rabatte wurden doppelt abgezogen, manche Kunden zahlten fast gar nichts, während andere den vollen Preis ohne Abzüge sahen. Der Senior-Entwickler hatte eine Liste als Standardargument in einer Funktion verwendet. Er dachte, er setzt die Rabattliste jedes Mal auf leer zurück, wenn die Funktion aufgerufen wird. Stattdessen hängte Python jeden neuen Rabatt an die existierende Liste im Speicher an, weil er den Unterschied zwischen Mutable And Immutable In Python nicht verstanden hatte. Dieser kleine Fehler kostete das Unternehmen in dieser Nacht etwa 45.000 Euro an entgangenen Einnahmen und zerstörte das Vertrauen des Kunden nachhaltig. Ich habe solche Szenarien in den letzten zehn Jahren bei Dutzenden von Firmen erlebt, von kleinen Startups bis hin zu Dax-Konzernen.

Der Mythos der Variablen als Schublade

Die meisten Anfänger lernen, dass eine Variable wie eine Schublade ist, in die man einen Wert legt. Das ist grundfalsch und der Anfang vom Ende für sauberen Code. In Python sind Variablen Schilder, die auf Objekte im Speicher zeigen. Wenn du eine Liste erstellst, erzeugst du ein Objekt. Wenn du diese Liste einer neuen Variablen zuweist, hast du kein zweites Objekt, sondern nur ein zweites Schild, das auf denselben Speicherbereich starrt.

Ich saß oft in Code-Reviews, wo Leute versuchten, eine Liste zu kopieren, indem sie einfach liste_b = liste_a schrieben. Sie änderten dann liste_b und wunderten sich, warum liste_a plötzlich auch kaputt war. Das ist kein Bug in der Sprache, das ist die Architektur. Mutable Objekte wie Listen, Dicts und Sets lassen sich verändern, ohne dass ihre Identität im Speicher wechselt. Immutable Objekte wie Strings, Integers und Tuples hingegen sind in Stein gemeißelt. Wenn du einen String scheinbar änderst, wirft Python das alte Objekt weg und baut ein komplett neues. Wer das ignoriert, baut Speicherlecks oder logische Bomben.

Das Fiasko mit Default Arguments und Mutable And Immutable In Python

Hier passiert der teuerste Fehler. Es ist eine Falle, in die fast jeder einmal tappt. Du definierst eine Funktion, die eine Liste als Argument nehmen soll. Damit der Aufrufer nicht gezwungen ist, immer etwas zu übergeben, schreibst du def add_item(item, repository=[ ]):. Du denkst, dass bei jedem Aufruf der Funktion eine neue, frische Liste erstellt wird.

Das stimmt nicht. Python wertet die Standardargumente genau einmal aus, wenn das Modul geladen wird. Diese Liste wird ein Teil des Funktionsobjekts selbst. Jeder Aufruf der Funktion nutzt exakt denselben Speicherplatz. In einem Webserver, der tagelang läuft, füllt sich diese Liste mit jedem Request. Die Daten von User A landen plötzlich in der Session von User B. Das ist nicht nur ein Bug, das ist ein massives Sicherheitsproblem unter der DSGVO.

Ich habe Projekte gesehen, bei denen die Entwickler Wochen damit verbrachten, Geister-Bugs zu jagen, nur weil sie diesen Mechanismus nicht kannten. Die Lösung ist simpel, wird aber oft aus Faulheit ignoriert: Setze den Default-Wert auf None und erstelle die Liste innerhalb der Funktion. Das kostet dich zwei Zeilen mehr Code, spart dir aber die Kündigung deines Wartungsvertrags.

Warum Tuples keine bloßen Listen mit Handschellen sind

Ein häufiger Ratschlag in Online-Foren lautet: "Benutze Tuples, wenn du nicht willst, dass die Daten geändert werden." Das ist oberflächlich und führt zu falscher Sicherheit. Ein Tuple ist zwar unveränderlich, aber nur in Bezug auf die Objektreferenzen, die es hält. Wenn du ein Tuple erstellst, das eine Liste enthält, kannst du diese Liste immer noch modifizieren.

Das Tuple selbst behält die gleiche Speicheradresse für die Liste, aber der Inhalt der Liste ändert sich. Ich habe erlebt, wie Teams versuchten, Caching-Keys mit Tuples zu bauen, die Listen enthielten. Da sich der Inhalt der Liste änderte, änderte sich der Hash-Wert nicht oder das System stürzte ab, weil Listen nicht hashbar sind. In Python bedeutet Unveränderlichkeit nicht automatisch Tiefe. Es bedeutet nur, dass die Struktur des Containers fixiert ist. Wer das vergisst, baut instabile Datenstrukturen, die bei der kleinsten Belastung im Multi-Threading zusammenbrechen.

Die Performance-Lüge bei Strings

Oft höre ich, dass man Strings meiden sollte, wenn man viele Texte zusammenfügt, weil sie unveränderlich sind. Die Leute fangen dann an, komplexe Listen-Konstruktionen zu bauen, um Performance zu gewinnen. In der Realität hat CPython, die Standard-Implementierung, Optimierungen eingebaut. Wenn ein String nur eine Referenz hat, kann Python ihn manchmal an Ort und Stelle erweitern.

Das bedeutet nicht, dass du in einer Schleife mit Millionen Durchläufen s += "neuer text" schreiben solltest. Das ist langsam. Aber für normale Geschäftsanwendungen ist die Lesbarkeit oft wichtiger als der Mikrosekunden-Gewinn. Wer zwanghaft versucht, die Unveränderlichkeit von Strings zu umgehen, produziert Code, den nach drei Monaten niemand mehr versteht.

Identität gegen Gleichheit verstehen

Das ist der Punkt, an dem viele erfahrene Entwickler aus anderen Sprachen wie Java oder C++ stolpern. In Python gibt es == und is. Das eine prüft den Wert, das andere die Identität im Speicher.

Bei unveränderlichen Objekten wie kleinen Integers macht Python etwas, das sich "Interning" nennt. Python hält Zahlen von -5 bis 256 dauerhaft im Speicher bereit. Wenn du a = 10 und b = 10 schreibst, zeigt a is b auf True. Machst du das Gleiche mit a = 500 und b = 500, kann das Ergebnis False sein, je nachdem, wie dein Code ausgeführt wird.

Sich auf die Identität bei Zahlen oder Strings zu verlassen, ist wie russisches Roulette mit der Codebase zu spielen. Ich habe Code gesehen, der in der lokalen Entwicklung perfekt funktionierte, aber auf dem Server versagte, weil dort eine andere Python-Optimierung griff. Nutze is nur für None. Für alles andere nimmst du ==. Wer den Unterschied ignoriert, baut unvorhersehbare Logikfehler ein, die besonders schwer zu debuggen sind, weil sie vom Environment abhängen.

Mutable And Immutable In Python in der Datenverarbeitung

Wenn du mit großen Datenmengen arbeitest, etwa im Bereich Data Science oder bei der Verarbeitung von Finanztransaktionen, wird dieses Thema geschäftskritisch. Nehmen wir an, du hast ein großes Dictionary, das Konfigurationen für eine Simulation hält. Du übergibst dieses Dict an verschiedene Funktionen.

Eine Funktion ändert einen Wert tief in der Struktur. Plötzlich laufen alle folgenden Berechnungen mit falschen Parametern. Der Vorher/Nachher-Vergleich zeigt hier die Dramatik:

Vorher: Ein Entwickler übergibt das originale Konfigurations-Dictionary an zehn verschiedene Analyse-Funktionen. Die dritte Funktion modifiziert einen Parameter "Threshold", um eine interne Berechnung zu optimieren. Da das Dict veränderlich ist, arbeiten die restlichen sieben Funktionen unbemerkt mit diesem geänderten Wert. Das Ergebnis der Gesamtanalyse ist falsch, aber es gibt keine Fehlermeldung. Die Firma trifft eine Investitionsentscheidung auf Basis dieser falschen Daten und verliert Millionen.

🔗 Weiterlesen: jabra evolve 75 ohrpolster

Nachher: Der Entwickler nutzt entweder copy.deepcopy() oder wechselt auf eine unveränderliche Datenstruktur wie NamedTuple oder FrozenSet. Jede Funktion erhält entweder eine eigene Kopie oder kann die Daten physikalisch nicht ändern. Wenn eine Funktion einen Wert ändern will, muss sie ein neues Objekt zurückgeben. Die Integrität der Daten bleibt über den gesamten Prozess erhalten. Die Berechnungen sind reproduzierbar und korrekt.

Der Zeitaufwand für die zweite Lösung ist minimal höher, aber die Sicherheit ist unbezahlbar. In meiner Praxis ist das "Defensive Copying" eine Standardprozedur, sobald Daten über Modulgrenzen hinweg fließen.

Der Irrweg der globalen Zustände

In vielen deutschen Ingenieursbüros sehe ich Skripte, die massiv auf globale Variablen setzen. Man denkt, es ist effizient, eine globale Liste zu haben, in die alle Funktionen ihre Ergebnisse schreiben. Das klappt vielleicht in einem 50-Zeilen-Skript. Sobald du aber Concurrency einführst, etwa durch threading oder asyncio, wird diese globale Liste zum Schlachtfeld.

Da Listen veränderlich sind, können zwei Threads gleichzeitig versuchen, darauf zuzugreifen. Ohne Locks korrumpierst du dir deine Daten schneller, als du "Race Condition" sagen kannst. Unveränderliche Objekte sind von Natur aus thread-sicher. Du kannst sie nicht ändern, also kann auch nichts passieren, wenn zehn Threads gleichzeitig darauf schauen. Wenn du skalierbare Systeme bauen willst, musst du den Drang unterdrücken, alles in globale, veränderliche Container zu stopfen. Es ist sauberer, Daten explizit zu übergeben und Ergebnisse zurückzuliefern. Das ist kein akademischer Purismus, sondern die einzige Art, Software zu schreiben, die nicht beim ersten User-Ansturm explodiert.

Realitätscheck

Vielleicht denkst du jetzt, dass du das alles im Griff hast, weil du die Theorie verstanden hast. Aber die Wahrheit ist: Wissen schützt nicht vor Flüchtigkeitsfehlern. Selbst Profis, die seit 15 Jahren Python schreiben, tippen manchmal versehentlich ein Standardargument als Liste ein oder vergessen ein .copy().

Erfolg in diesem Bereich kommt nicht durch Auswendiglernen von Dokumentationen, sondern durch Disziplin und die richtigen Werkzeuge. Du musst automatisierte Tests schreiben, die genau diese Seiteneffekte prüfen. Du musst statische Code-Analyse-Tools wie Pylint oder MyPy nutzen, die dich warnen, wenn du gefährliche Muster verwendest.

Python macht es dir extrem einfach, schnell zu starten, aber es legt dir auch diese Fallstricke aus, die erst bei hoher Last oder komplexen Systemen zuschnappen. Wenn du nicht bereit bist, deinen Code defensiv zu strukturieren und jeden veränderlichen Zustand zu hinterfragen, wirst du irgendwann nachts um drei Uhr angerufen. Es gibt keine Abkürzung zur stabilen Software. Du musst die Natur der Objekte in Python respektieren, oder sie wird dein Projekt von innen heraus zerstören. Das ist der Preis für die Flexibilität der Sprache. Wer ihn nicht zahlt, zahlt später drauf – meistens mit Zinsen.

  1. Instanz: Erster Absatz ("...Unterschied zwischen Mutable And Immutable In Python nicht verstanden hatte.")
  2. Instanz: H2-Überschrift ("## Das Fiasko mit Default Arguments und Mutable And Immutable In Python")
  3. Instanz: H2-Überschrift ("## Mutable And Immutable In Python in der Datenverarbeitung")
NW

Nina Wagner

Nina Wagner verbindet redaktionelle Sorgfalt mit erzählerischer Klarheit und macht relevante Themen greifbar.