Python & AI Tutorials Logo
Python Programmierung

18. Python-Daten- und Objektmodell: Referenzen, Vergleiche und Kopien

Zu verstehen, wie Python Daten speichert und verwaltet, ist entscheidend, um korrekte Programme zu schreiben. In diesem Kapitel werden wir Pythons Objektmodell (object model) erkunden – das grundlegende System, das bestimmt, wie alle Daten in Python funktionieren. Sie werden lernen, warum einige Zuweisungen unabhängige Kopien erzeugen, während andere gemeinsame Referenzen erstellen, wie man Objekte korrekt vergleicht und wie man häufige Fallstricke beim Arbeiten mit Collections vermeidet.

Dieses Wissen wird Ihnen helfen, überraschende Verhaltensweisen zu verstehen, die Ihnen vielleicht schon begegnet sind – zum Beispiel, warum das Ändern einer Liste manchmal eine andere beeinflusst oder warum der Vergleich zweier Listen mit == andere Ergebnisse liefert als der Vergleich mit is.

18.1) Alles ist ein Objekt in Python

In Python ist jedes Datenstück ein Objekt (object). Das ist nicht nur ein theoretisches Konzept – es hat praktische Auswirkungen darauf, wie Ihre Programme funktionieren.

Wenn Sie eine Zahl, eine Zeichenkette, eine Liste oder irgendeinen anderen Wert erstellen, erzeugt Python ein Objekt im Speicher. Ein Objekt ist ein Container, der Folgendes enthält:

  • Die tatsächlichen Daten (der Wert (value))
  • Informationen darüber, um welchen Datentyp es sich handelt (der Typ (type))
  • Einen eindeutigen Bezeichner (die Identität (identity))

Sehen wir uns das in der Praxis an:

python
# Erstellen verschiedener Objekttypen
number = 42
text = "Hello"
items = [1, 2, 3]
 
# Jede dieser Variablen verweist auf ein Objekt im Speicher
print(number)  # Output: 42
print(text)    # Output: Hello
print(items)   # Output: [1, 2, 3]

Selbst einfache Werte wie Ganzzahlen sind Objekte. Das bedeutet, sie haben Fähigkeiten, die über das bloße Speichern einer Zahl hinausgehen:

python
# Ganzzahlen sind Objekte mit Methoden
number = 42
print(number.bit_length())  # Output: 6
 
# Zeichenketten sind Objekte mit Methoden
text = "hello"
print(text.upper())  # Output: HELLO
 
# Listen sind Objekte mit Methoden
items = [3, 1, 2]
items.sort()
print(items)  # Output: [1, 2, 3]

Warum ist das wichtig? Weil Sie, wenn Sie eine Variable zuweisen oder Daten an eine Funktion übergeben, das Objekt nicht kopieren – Sie erstellen eine Referenz (reference) auf dasselbe Objekt. Das unterscheidet sich grundlegend von der Funktionsweise mancher anderer Programmiersprachen, und das Verständnis dieses Unterschieds verhindert viele verwirrende Bugs.

python
# Erstellen eines Listenobjekts
original = [1, 2, 3]
 
# Das erstellt keine neue Liste – es erstellt eine weitere Referenz
# auf das GLEICHE Listenobjekt
another_name = original
 
# Eine Änderung über eine Referenz wirkt sich auf die andere aus
another_name.append(4)
 
print(original)      # Output: [1, 2, 3, 4]
print(another_name)  # Output: [1, 2, 3, 4]

Sowohl original als auch another_name verweisen auf dasselbe Listenobjekt im Speicher. Wenn wir die Liste über another_name ändern, sehen wir die Änderung über original, weil beide auf dasselbe Objekt schauen.

Variable: original

Listenobjekt: 1, 2, 3, 4

Variable: another_name

Dieses Verhalten nennt man Referenzsemantik (reference semantics), und es ist eines der wichtigsten Konzepte in der Python-Programmierung. Wir werden es in diesem Kapitel ausführlich untersuchen.

18.2) Identität, Typ und Wert von Objekten

Jedes Objekt in Python hat drei grundlegende Eigenschaften, die es definieren: Identität, Typ und Wert. Wenn Sie diese Eigenschaften verstehen, können Sie besser nachvollziehen, wie sich Objekte verhalten und wie Sie sie korrekt vergleichen.

18.2.1) Objektidentität mit id()

Die Identität (identity) eines Objekts ist eine eindeutige Zahl, die Python beim Erstellen des Objekts vergibt. Diese Identität ändert sich während der Lebensdauer des Objekts nie – sie ist wie eine feste Adresse im Speicher.

Sie können die Identität eines Objekts mit der Funktion id() abrufen:

python
# Objekte erstellen und ihre Identitäten prüfen
x = [1, 2, 3]
y = [1, 2, 3]
z = x
 
print(id(x))  # Output: 140234567890123 (Beispiel – tatsächliche Zahl variiert)
print(id(y))  # Output: 140234567890456 (anders als x)
print(id(z))  # Output: 140234567890123 (gleich wie x)

Die tatsächlichen Zahlen, die Sie sehen, sind bei jedem Programmlauf anders, aber das Muster bleibt: x und y haben unterschiedliche Identitäten, weil sie unterschiedliche Objekte sind, obwohl sie dieselben Werte enthalten. Dagegen hat z dieselbe Identität wie x, weil z nur ein anderer Name für dasselbe Objekt ist.

Hier ist ein praktisches Beispiel, das zeigt, warum Identität wichtig ist:

python
# Zwei Schüler mit denselben Noten
student1_grades = [85, 90, 92]
student2_grades = [85, 90, 92]
 
# Das sind unterschiedliche Objekte (unterschiedliche Identitäten)
print(id(student1_grades))  # Output: 140234567890123 (Beispiel)
print(id(student2_grades))  # Output: 140234567890456 (anders)
 
# Eine Änderung an einem beeinflusst das andere nicht
student1_grades.append(88)
print(student1_grades)  # Output: [85, 90, 92, 88]
print(student2_grades)  # Output: [85, 90, 92]

Betrachten Sie nun ein anderes Szenario:

python
# Die Noten eines Schülers werden von zwei Variablen nachverfolgt
original_grades = [85, 90, 92]
backup_reference = original_grades
 
# Diese verweisen auf das GLEICHE Objekt (gleiche Identität)
print(id(original_grades))    # Output: 140234567890123 (Beispiel)
print(id(backup_reference))   # Output: 140234567890123 (gleich!)
 
# Eine Änderung über einen der Namen beeinflusst beide
backup_reference.append(88)
print(original_grades)     # Output: [85, 90, 92, 88]
print(backup_reference)    # Output: [85, 90, 92, 88]

Kernaussage: Wenn zwei Variablen dieselbe Identität haben, verweisen sie auf exakt dasselbe Objekt im Speicher. Änderungen, die über eine Variable vorgenommen werden, sind über die andere sichtbar, weil nur ein Objekt geändert wird.

18.2.2) Objekttyp mit type()

Der Typ (type) eines Objekts bestimmt, welche Art von Daten es enthält und welche Operationen Sie darauf ausführen können. Wie wir in Kapitel 3 gelernt haben, können Sie den Typ eines Objekts mit der Funktion type() prüfen:

python
# Verschiedene Objekttypen
number = 42
text = "Hello"
items = [1, 2, 3]
mapping = {"name": "Alice"}
 
print(type(number))   # Output: <class 'int'>
print(type(text))     # Output: <class 'str'>
print(type(items))    # Output: <class 'list'>
print(type(mapping))  # Output: <class 'dict'>

Der Typ eines Objekts ändert sich nach der Erstellung nie. Sie können aus einer Ganzzahl keine Zeichenkette machen – Sie können nur ein neues Zeichenkettenobjekt basierend auf dem Wert der Ganzzahl erstellen:

python
# Der Typ ist bei der Erstellung festgelegt
x = 42
print(type(x))  # Output: <class 'int'>
 
# Das ändert nicht den Typ von x – es erstellt ein NEUES Zeichenkettenobjekt
# und lässt x stattdessen auf dieses neue Objekt verweisen
x = str(x)
# Das ursprüngliche Ganzzahlobjekt (42) existiert im Speicher weiter, bis es vom Garbage Collector bereinigt (garbage collected) wird
# x zeigt jetzt auf ein völlig anderes Objekt: die Zeichenkette "42"
 
print(type(x))  # Output: <class 'str'>
print(x)        # Output: 42 (jetzt eine Zeichenkette, keine Ganzzahl)

Das Verständnis von Typen ist entscheidend, weil unterschiedliche Typen unterschiedliche Operationen unterstützen:

python
# Listen unterstützen append
grades = [85, 90]
grades.append(92)
print(grades)  # Output: [85, 90, 92]
 
# Zeichenketten haben kein append – sie sind immutable
text = "Hello"
# text.append(" World")  # AttributeError: 'str' object has no attribute 'append'
 
# Aber Zeichenketten unterstützen Konkatenation
text = text + " World"
print(text)  # Output: Hello World

18.2.3) Objektwert

Der Wert (value) eines Objekts sind die tatsächlichen Daten, die es enthält. Im Gegensatz zu Identität und Typ kann sich der Wert bei mutablen Objekten (wie Listen und Dictionaries) ändern, kann sich aber bei immutablen Objekten (wie Ganzzahlen und Zeichenketten) nicht ändern.

python
# Bei mutablen Objekten kann sich der Wert ändern
shopping_cart = ["milk", "bread"]
print(shopping_cart)  # Output: ['milk', 'bread']
 
shopping_cart.append("eggs")
print(shopping_cart)  # Output: ['milk', 'bread', 'eggs']
# Gleiches Objekt (gleiche Identität), anderer Wert
 
# Bei immutablen Objekten kann sich der Wert nicht ändern
count = 5
print(count)  # Output: 5
 
count = count + 1
print(count)  # Output: 6
# Das hat ein NEUES Objekt mit einer neuen Identität erstellt

Hier ist ein vollständiges Beispiel, das alle drei Eigenschaften zeigt:

python
# Erstellen eines Listenobjekts
data = [10, 20, 30]
 
print("Identity:", id(data))      # Output: Identity: 140234567890123 (Beispiel)
print("Type:", type(data))        # Output: Type: <class 'list'>
print("Value:", data)             # Output: Value: [10, 20, 30]
 
# Ändern des Werts (Identität und Typ bleiben gleich)
data.append(40)
 
print("Identity:", id(data))      # Output: Identity: 140234567890123 (unchanged)
print("Type:", type(data))        # Output: Type: <class 'list'> (unchanged)
print("Value:", data)             # Output: Value: [10, 20, 30, 40] (changed)

Objekt

Identität: Eindeutige ID

Typ: class 'list'

Wert: 10, 20, 30, 40

Ändert sich nie

Ändert sich nie

Kann sich bei mutablen Typen ändern

Wenn Sie diese drei Eigenschaften verstehen, können Sie vorhersagen, wie sich Objekte in Ihren Programmen verhalten. Die Identität sagt Ihnen, ob zwei Variablen auf dasselbe Objekt verweisen, der Typ sagt Ihnen, welche Operationen erlaubt sind, und der Wert sagt Ihnen, welche Daten das Objekt aktuell enthält.

18.3) Mutable und Immutable Typen

Eine der wichtigsten Unterscheidungen in Python ist die zwischen mutablen (mutable) und immutablen (immutable) Typen. Diese Unterscheidung beeinflusst, wie sich Objekte verhalten, wenn Sie versuchen, sie zu ändern, und sie zu verstehen verhindert viele häufige Programmierfehler.

18.3.1) Immutable Typen: Werte, die sich nicht ändern können

Ein immutables Objekt ist eines, dessen Wert nach der Erstellung nicht mehr geändert werden kann. Wenn Sie eine Operation ausführen, die so aussieht, als würde sie ein immutables Objekt ändern, erstellt Python tatsächlich ein neues Objekt mit dem geänderten Wert.

Zu Pythons immutablen Typen gehören:

  • Ganzzahlen (int)
  • Gleitkommazahlen (float)
  • Zeichenketten (str)
  • Tupel (tuple)
  • Booleans (bool)
  • None (NoneType)

Sehen wir uns Immutabilität bei Ganzzahlen an:

python
# Erstellen einer Ganzzahl
x = 100
print("Original x:", x)           # Output: Original x: 100
print("Identity of x:", id(x))    # Output: Identity of x: 140234567890123 (Beispiel)
 
# Das sieht so aus, als würden wir x ändern, aber wir erstellen tatsächlich ein neues Objekt
x = x + 1
print("Modified x:", x)           # Output: Modified x: 101
print("Identity of x:", id(x))    # Output: Identity of x: 140234567890456 (different!)

Die Identität hat sich geändert, weil x = x + 1 ein komplett neues Ganzzahlobjekt mit dem Wert 101 erstellt hat. Das ursprüngliche Objekt mit dem Wert 100 existiert weiterhin (bis Pythons Garbage Collector es entfernt), aber x verweist jetzt auf ein anderes Objekt.

Zeichenketten zeigen Immutabilität noch deutlicher:

python
# Erstellen einer Zeichenkette
message = "Hello"
print("Original:", message)        # Output: Original: Hello
print("Identity:", id(message))    # Output: Identity: 140234567890789 (Beispiel)
 
# Zeichenkettenmethoden ändern das Original nicht – sie geben neue Zeichenketten zurück
uppercase = message.upper()
print("Original:", message)        # Output: Original: Hello (unchanged)
print("Uppercase:", uppercase)     # Output: Uppercase: HELLO
print("Identity of original:", id(message))    # Output: Identity of original: 140234567890789 (same)
print("Identity of uppercase:", id(uppercase)) # Output: Identity of uppercase: 140234567891012 (different)

Selbst Operationen, die so aussehen, als würden sie eine Zeichenkette ändern, erstellen tatsächlich neue Zeichenkettenobjekte:

python
# Eine Zeichenkette durch Konkatenation aufbauen
text = "Python"
print("Before:", text, "- ID:", id(text))  # Output: Before: Python - ID: 140234567891234 (Beispiel)
 
text = text + " Programming"
print("After:", text, "- ID:", id(text))   # Output: After: Python Programming - ID: 140234567891567 (different)

Warum Immutabilität wichtig ist: Immutable Objekte lassen sich sicher zwischen verschiedenen Teilen Ihres Programms teilen, weil kein Teil sie versehentlich ändern kann. Das macht Ihren Code vorhersehbarer und leichter nachzuvollziehen.

18.3.2) Mutable Typen: Werte, die sich ändern können

Ein mutables Objekt ist eines, dessen Wert nach der Erstellung geändert werden kann, ohne ein neues Objekt zu erstellen. Die Identität des Objekts bleibt gleich, aber sein Inhalt kann verändert werden.

Zu Pythons mutablen Typen gehören:

  • Listen (list)
  • Dictionaries (dict)
  • Sets (set)

Sehen wir uns Mutabilität bei Listen an:

python
# Erstellen einer Liste
numbers = [1, 2, 3]
print("Original:", numbers)        # Output: Original: [1, 2, 3]
print("Identity:", id(numbers))    # Output: Identity: 140234567892345 (Beispiel)
 
# Liste ändern – gleiches Objekt, anderer Wert
numbers.append(4)
print("Modified:", numbers)        # Output: Modified: [1, 2, 3, 4]
print("Identity:", id(numbers))    # Output: Identity: 140234567892345 (same!)

Die Identität hat sich nicht geändert, weil wir das bestehende Listenobjekt geändert haben, statt ein neues zu erstellen. Das ist grundlegend anders als bei immutablen Typen.

Dictionaries und Sets sind ebenfalls mutable:

python
# Dictionary-Beispiel
student = {"name": "Alice", "grade": 85}
print("Before:", student, "- ID:", id(student))  # Output: Before: {'name': 'Alice', 'grade': 85} - ID: 140234567893012 (Beispiel)
 
student["grade"] = 90  # Das Dictionary ändern
print("After:", student, "- ID:", id(student))   # Output: After: {'name': 'Alice', 'grade': 90} - ID: 140234567893012 (same)
 
# Set-Beispiel
unique_numbers = {1, 2, 3}
print("Before:", unique_numbers, "- ID:", id(unique_numbers))  # Output: Before: {1, 2, 3} - ID: 140234567893345 (Beispiel)
 
unique_numbers.add(4)  # Das Set ändern
print("After:", unique_numbers, "- ID:", id(unique_numbers))   # Output: After: {1, 2, 3, 4} - ID: 140234567893345 (same)

18.3.3) Warum Mutabilität in der Praxis wichtig ist

Der Unterschied zwischen mutablen und immutablen Typen wird kritisch, wenn mehrere Variablen auf dasselbe Objekt verweisen:

python
# Immutable Beispiel – sicheres Teilen
x = "Hello"
y = x  # y verweist auf dasselbe Zeichenkettenobjekt
 
# "Ändern" von x erstellt ein neues Objekt
x = x + " World"
 
print(x)  # Output: Hello World
print(y)  # Output: Hello (unchanged - y still refers to the original)
python
# Mutable Beispiel – geteilte Änderungen
list1 = [1, 2, 3]
list2 = list1  # list2 verweist auf das GLEICHE Listenobjekt
 
# Eine Änderung über list1 beeinflusst list2
list1.append(4)
 
print(list1)  # Output: [1, 2, 3, 4]
print(list2)  # Output: [1, 2, 3, 4] (also changed!)

Immutable Typen

int, float, str, tuple, bool, None

Wert kann sich nicht ändern

Operationen erstellen neue Objekte

Sicher zu teilen

Mutable Typen

list, dict, set

Wert kann sich ändern

Operationen ändern das bestehende Objekt

Teilen erfordert Vorsicht

Das Verständnis von Mutabilität ist essenziell für:

  1. Verhalten vorhersagen: Zu wissen, ob eine Operation ein neues Objekt erstellt oder ein bestehendes ändert
  2. Bugs vermeiden: Unbeabsichtigte Änderungen verhindern, wenn Objekte geteilt werden
  3. Effizienten Code schreiben: Den richtigen Typ für Ihren Anwendungsfall wählen
  4. Funktionsverhalten verstehen: Zu wissen, wann Funktionsparameter geändert werden können

In den nächsten Abschnitten werden wir untersuchen, wie Zuweisung mit diesen unterschiedlichen Typen funktioniert und wie Sie bei Bedarf unabhängige Kopien erstellen.

18.4) Wie Zuweisung mit Objekten funktioniert

Zuweisung in Python kopiert keine Objekte – sie erstellt Referenzen auf Objekte. Diesen Unterschied zu verstehen, ist entscheidend, um korrekte Programme zu schreiben, insbesondere beim Arbeiten mit mutablen Typen.

18.4.1) Zuweisung erstellt Referenzen, keine Kopien

Wenn Sie x = y schreiben, erstellt Python keine Kopie des Objekts, auf das y verweist. Stattdessen lässt es x auf dasselbe Objekt verweisen wie y. Beide Variablen werden zu Namen für dasselbe Objekt im Speicher.

Sehen wir uns das zuerst mit immutablen Objekten an:

python
# Zuweisung mit Ganzzahlen (immutable)
a = 100
b = a  # b verweist jetzt auf dasselbe Ganzzahlobjekt wie a
 
print("a:", a)           # Output: a: 100
print("b:", b)           # Output: b: 100
print("Same object?", id(a) == id(b))  # Output: Same object? True
 
# "Ändern" von a erstellt ein neues Objekt
a = a + 1
 
print("a:", a)           # Output: a: 101
print("b:", b)           # Output: b: 100 (unchanged)
print("Same object?", id(a) == id(b))  # Output: Same object? False

Bei immutablen Objekten ist dieses Verhalten normalerweise sicher, weil Sie das ursprüngliche Objekt nicht ändern können. Wenn Sie eine Operation ausführen, die den Wert ändert, erstellt Python ein neues Objekt.

Bei mutablen Objekten ist das Verhalten jedoch sehr anders:

python
# Zuweisung mit Listen (mutable)
list1 = [1, 2, 3]
list2 = list1  # list2 verweist auf das GLEICHE Listenobjekt wie list1
 
print("list1:", list1)   # Output: list1: [1, 2, 3]
print("list2:", list2)   # Output: list2: [1, 2, 3]
print("Same object?", id(list1) == id(list2))  # Output: Same object? True
 
# Eine Änderung über list1 beeinflusst list2
list1.append(4)
 
print("list1:", list1)   # Output: list1: [1, 2, 3, 4]
print("list2:", list2)   # Output: list2: [1, 2, 3, 4] (also changed!)
print("Same object?", id(list1) == id(list2))  # Output: Same object? True

Sowohl list1 als auch list2 sind Namen für dasselbe Listenobjekt. Wenn Sie die Liste über einen der Namen ändern, sehen Sie die Änderung über beide, weil es nur eine Liste gibt.

Zuweisung mit immutablen Typen

Beide Variablen verweisen anfangs auf dasselbe Objekt

Operationen erstellen neue Objekte

Variablen werden unabhängig

Zuweisung mit mutablen Typen

Beide Variablen verweisen auf dasselbe Objekt

Operationen ändern das geteilte Objekt

Änderungen sind über beide Variablen sichtbar

Hier ist ein praktisches Beispiel, das zeigt, warum das wichtig ist:

python
# Notenverwaltung für einen Schüler
alice_grades = [85, 90, 92]
backup_grades = alice_grades  # Versuch, ein Backup zu erstellen
 
print("Original:", alice_grades)  # Output: Original: [85, 90, 92]
print("Backup:", backup_grades)   # Output: Backup: [85, 90, 92]
 
# Eine neue Note hinzufügen
alice_grades.append(88)
 
# Das „Backup“ wurde ebenfalls geändert!
print("Original:", alice_grades)  # Output: Original: [85, 90, 92, 88]
print("Backup:", backup_grades)   # Output: Backup: [85, 90, 92, 88]

Das ist überhaupt kein Backup – beide Variablen verweisen auf dieselbe Liste. Um ein echtes Backup zu erstellen, müssen Sie eine Kopie erstellen (das behandeln wir in Abschnitt 18.8).

18.4.2) Zuweisung bei Funktionsaufrufen

Wenn Sie ein Argument an eine Funktion übergeben, verwendet Python dieselbe Referenzsemantik. Der Parameter wird zu einem weiteren Namen für dasselbe Objekt:

python
# Funktion mit immutablem Parameter
def increment(number):
    number = number + 1  # Erstellt ein neues Objekt
    return number
 
value = 5
result = increment(value)
 
print("Original value:", value)    # Output: Original value: 5 (unchanged)
print("Returned result:", result)  # Output: Returned result: 6

Der Parameter number verweist anfangs auf dasselbe Ganzzahlobjekt wie value. Wenn wir number = number + 1 ausführen, erstellen wir ein neues Ganzzahlobjekt und lassen number darauf verweisen. Das ursprüngliche Objekt (und value) bleiben unverändert.

Bei mutablen Objekten ist das Verhalten anders:

python
# Funktion mit mutablem Parameter
def add_item(items, new_item):
    items.append(new_item)  # Ändert die ursprüngliche Liste
 
shopping_list = ["milk", "bread"]
add_item(shopping_list, "eggs")
 
print("Original list:", shopping_list)  # Output: Original list: ['milk', 'bread', 'eggs']

Der Parameter items verweist auf dasselbe Listenobjekt wie shopping_list. Wenn wir die Liste über items ändern, ändern wir die ursprüngliche Liste.

Hier ist ein häufiger Fehler und wie Sie ihn vermeiden:

python
# FEHLER: Unbeabsichtigtes Ändern des Originals
def process_grades(grades):
    grades.append(100)  # Ändert das Original!
    return grades
 
student_grades = [85, 90, 92]
processed = process_grades(student_grades)
 
print("Original:", student_grades)  # Output: Original: [85, 90, 92, 100] (modified!)
print("Processed:", processed)      # Output: Processed: [85, 90, 92, 100]
 
# KORREKT: Erstellen Sie eine Kopie, wenn Sie das Original nicht ändern wollen
def process_grades_safely(grades):
    # Erstellen Sie eine neue Liste mit denselben Elementen
    result = grades + [100]  # Konkatenation erstellt eine neue Liste
    return result
 
student_grades = [85, 90, 92]
processed = process_grades_safely(student_grades)
 
print("Original:", student_grades)  # Output: Original: [85, 90, 92] (unchanged)
print("Processed:", processed)      # Output: Processed: [85, 90, 92, 100]

Wichtiger Hinweis zu mutablen Standardargumenten: Ein verwandter, häufiger Fallstrick besteht darin, mutable Objekte als Standardwerte für Parameter zu verwenden (wie def func(items=[]):). Standardparameter werden einmal erstellt, wenn die Funktion definiert wird, nicht jedes Mal, wenn sie aufgerufen wird, was zu unerwartetem Verhalten führen kann, bei dem die Standardliste über mehrere Funktionsaufrufe hinweg Werte ansammelt. Wir werden das in Kapitel 20 im Detail untersuchen, aber beachten Sie, dass dies eine häufige Fehlerquelle ist, wenn Sie mit mutablen Parametern arbeiten.

18.5) Referenzsemantik und Objekt-Aliasing

Referenzsemantik bedeutet, dass Variablen in Python Namen sind, die auf Objekte verweisen, nicht Container, die Werte halten. Wenn mehrere Variablen auf dasselbe Objekt verweisen, nennen wir das Aliasing (aliasing). Aliasing zu verstehen ist essenziell, um vorherzusagen, wie sich Ihre Programme verhalten.

18.5.1) Was ist Aliasing?

Aliasing tritt auf, wenn zwei oder mehr Variablen auf dasselbe Objekt im Speicher verweisen. Die Variablen sind „Aliase“ füreinander – unterschiedliche Namen für dasselbe.

Sehen wir uns Aliasing an einem einfachen Beispiel an:

python
# Eine Liste und einen Alias erstellen
original = [1, 2, 3]
alias = original  # alias verweist auf dieselbe Liste wie original
 
print("Original:", original)  # Output: Original: [1, 2, 3]
print("Alias:", alias)        # Output: Alias: [1, 2, 3]
print("Same object?", id(original) == id(alias))  # Output: Same object? True
 
# Über den Alias ändern
alias.append(4)
 
# Die Änderung ist über beide Namen sichtbar
print("Original:", original)  # Output: Original: [1, 2, 3, 4]
print("Alias:", alias)        # Output: Alias: [1, 2, 3, 4]

Es gibt nur ein Listenobjekt im Speicher, aber es hat zwei Namen: original und alias. Jede Änderung, die über einen der Namen vorgenommen wird, wirkt sich auf dasselbe zugrunde liegende Objekt aus.

Hier ist ein realistischeres Beispiel mit Schülerdatensätzen:

python
# Schülerdatenbank mit Aliasing
students = {
    "alice": {"name": "Alice", "grade": 85},
    "bob": {"name": "Bob", "grade": 90}
}
 
# Einen Alias auf Alices Eintrag erstellen
alice_record = students["alice"]
 
print("Alice's grade:", alice_record["grade"])  # Output: Alice's grade: 85
 
# Über den Alias ändern
alice_record["grade"] = 95
 
# Die Änderung ist im ursprünglichen Dictionary sichtbar
print("Updated grade:", students["alice"]["grade"])  # Output: Updated grade: 95

Die Variable alice_record ist ein Alias für das Dictionary, das unter students["alice"] gespeichert ist. Wenn wir alice_record ändern, ändern wir dasselbe Dictionary, das im students-Dictionary gespeichert ist.

18.5.2) Aliasing mit dem Operator is erkennen

Sie können prüfen, ob zwei Variablen Aliase sind (auf dasselbe Objekt verweisen), indem Sie den Operator is verwenden:

python
# Auf Aliasing prüfen
list1 = [1, 2, 3]
list2 = list1      # Alias
list3 = [1, 2, 3]  # Anderes Objekt mit demselben Wert
 
print("list1 is list2:", list1 is list2)  # Output: list1 is list2: True (aliases)
print("list1 is list3:", list1 is list3)  # Output: list1 is list3: False (different objects)
print("list1 == list3:", list1 == list3)  # Output: list1 == list3: True (same value)

Der Operator is prüft Identität (ob zwei Variablen auf dasselbe Objekt verweisen), während == den Wert prüft (ob zwei Objekte denselben Inhalt haben). Wir werden diese Unterscheidung in Abschnitt 18.6 im Detail betrachten.

18.5.3) Aliasing in Collections

Aliasing wird komplexer, wenn Objekte in Collections gespeichert werden:

python
# Eine Liste von Listen erstellen
row = [0, 0, 0]
grid = [row, row, row]  # Alle drei Elemente sind Aliase derselben Liste!
 
print("Grid:")
for r in grid:
    print(r)
# Output:
# [0, 0, 0]
# [0, 0, 0]
# [0, 0, 0]
 
# Das Ändern eines Elements beeinflusst alle Zeilen
grid[0][0] = 1
 
print("\nAfter modification:")
for r in grid:
    print(r)
# Output:
# [1, 0, 0]
# [1, 0, 0]
# [1, 0, 0]

Das ist ein häufiger Fehler, wenn man versucht, ein 2D-Grid zu erstellen. Alle drei Zeilen sind Aliase derselben Liste, daher ändert das Modifizieren einer Zeile alle.

Die korrekte Art, unabhängige Zeilen zu erstellen:

python
# Unabhängige Zeilen erstellen
grid = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]  # Jede Zeile ist eine separate Liste
 
print("Grid:")
for r in grid:
    print(r)
# Output:
# [0, 0, 0]
# [0, 0, 0]
# [0, 0, 0]
 
# Jetzt beeinflusst das Ändern eines Elements nur diese Zeile
grid[0][0] = 1
 
print("\nAfter modification:")
for r in grid:
    print(r)
# Output:
# [1, 0, 0]
# [0, 0, 0]
# [0, 0, 0]

18.6) Gleichheit, Identität und Mitgliedschaft (==, is und in) über Typen hinweg

Python stellt drei grundlegende Operatoren bereit, um Objekte zu vergleichen und Beziehungen zwischen ihnen zu prüfen: == für Gleichheit, is für Identität und in für Mitgliedschaft. Zu verstehen, wann Sie welchen Operator verwenden, ist entscheidend, um korrekte Programme zu schreiben.

18.6.1) Gleichheit mit == (Werte vergleichen)

Der Operator == prüft, ob zwei Objekte denselben Wert haben. Es spielt keine Rolle, ob es dasselbe Objekt im Speicher ist – nur, ob ihr Inhalt gleich ist.

python
# Werte mit == vergleichen
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1
 
print(list1 == list2)  # Output: True (same values)
print(list1 == list3)  # Output: True (same values)

Auch wenn list1 und list2 unterschiedliche Objekte im Speicher sind, haben sie denselben Wert, daher liefert == True.

So funktioniert == mit unterschiedlichen Typen:

python
# Gleichheit über verschiedene Typen hinweg
print(42 == 42)              # Output: True (same integer value)
print(42 == 42.0)            # Output: True (integer equals float with same value)
print("hello" == "hello")    # Output: True (same string value)
print([1, 2] == [1, 2])      # Output: True (same list contents)
print({"a": 1} == {"a": 1})  # Output: True (same dictionary contents)
 
# Unterschiedliche Werte
print(42 == 43)              # Output: False
print("hello" == "Hello")    # Output: False (case-sensitive)
print([1, 2] == [2, 1])      # Output: False (order matters)

Bei Collections führt == einen tiefen Vergleich (deep comparison) durch – es prüft, ob alle Elemente gleich sind:

python
# Tiefer Vergleich mit verschachtelten Strukturen
list1 = [[1, 2], [3, 4]]
list2 = [[1, 2], [3, 4]]
 
print(list1 == list2)  # Output: True (all nested elements are equal)
 
# Selbst wenn die inneren Listen unterschiedliche Objekte sind
print(id(list1[0]) == id(list2[0]))  # Output: False (different objects)
print(list1[0] == list2[0])          # Output: True (same values)

18.6.2) Identität mit is (Objektidentität vergleichen)

Der Operator is prüft, ob zwei Variablen auf dasselbe Objekt im Speicher verweisen. Er vergleicht Identitäten, nicht Werte.

python
# Identitäten mit is vergleichen
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1
 
print(list1 is list2)  # Output: False (different objects)
print(list1 is list3)  # Output: True (same object)
 
# Mit id() bestätigen
print(id(list1) == id(list2))  # Output: False
print(id(list1) == id(list3))  # Output: True

Wann is verwenden: Die häufigste Verwendung von is ist das Prüfen auf None:

python
# Auf None prüfen (die richtige Art)
def find_student(name, students):
    """Return student record or None if not found."""
    for student in students:
        if student["name"] == name:
            return student
    return None
 
students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 90}
]
 
result = find_student("Charlie", students)
 
# Verwenden Sie 'is', um auf None zu prüfen
if result is None:
    print("Student not found")  # Output: Student not found
else:
    print(f"Found: {result}")

18.6.3) Mitgliedschaft mit in (Enthaltensein prüfen)

Der Operator in prüft, ob ein Wert in einer Collection enthalten ist. Er funktioniert mit Zeichenketten, Listen, Tupeln, Sets und Dictionaries:

python
# Mitgliedschaft in verschiedenen Typen
print(2 in [1, 2, 3])           # Output: True
print("hello" in "hello world")  # Output: True
print("x" in {"x": 10, "y": 20}) # Output: True (checks keys)
print(5 in {1, 2, 3, 4, 5})     # Output: True

Bei Dictionaries prüft in, ob ein Schlüssel existiert:

python
# Mitgliedschaft in Dictionaries prüfen
student = {"name": "Alice", "grade": 85, "age": 20}
 
print("name" in student)    # Output: True (key exists)
print("Alice" in student)   # Output: False (value, not key)
print("grade" in student)   # Output: True (key exists)
 
# Für Werte muss man auf .values() zugreifen
print("Alice" in student.values())  # Output: True

Der Operator not in prüft auf Abwesenheit:

python
# Auf Abwesenheit prüfen
shopping_list = ["milk", "bread", "eggs"]
 
if "butter" not in shopping_list:
    print("Don't forget to buy butter!")  # Output: Don't forget to buy butter!

Zusammenfassung, wann Sie welchen Operator verwenden:

  • Verwenden Sie ==, wenn Sie prüfen wollen, ob zwei Objekte denselben Wert haben
  • Verwenden Sie is, wenn Sie prüfen wollen, ob zwei Variablen auf dasselbe Objekt verweisen (am häufigsten mit None oder beim Debuggen von Aliasing)
  • Verwenden Sie in, wenn Sie prüfen wollen, ob ein Wert in einer Collection enthalten ist

Wenn Sie diese Unterschiede verstehen, können Sie präzisere und korrektere Vergleiche in Ihren Programmen schreiben.

18.7) Objekte vergleichen, die andere Objekte enthalten

Wenn Objekte andere Objekte enthalten (wie Listen in Listen oder Dictionaries, die Listen enthalten), werden Vergleiche differenzierter. Zu verstehen, wie Python verschachtelte Strukturen vergleicht, ist essenziell für die Arbeit mit komplexen Daten.

18.7.1) Wie == mit verschachtelten Strukturen funktioniert

Der Operator == führt bei verschachtelten Strukturen einen rekursiven Vergleich (recursive comparison) durch. Er vergleicht nicht nur den äußeren Container, sondern auch alle verschachtelten Objekte:

python
# Verschachtelte Listen vergleichen
list1 = [[1, 2], [3, 4]]
list2 = [[1, 2], [3, 4]]
 
print(list1 == list2)  # Output: True
 
# Obwohl die inneren Listen unterschiedliche Objekte sind
print(id(list1[0]) == id(list2[0]))  # Output: False
print(list1[0] == list2[0])          # Output: True

Python vergleicht jedes Element rekursiv. Damit list1 == list2 True ist, muss jedes entsprechende Element gleich sein, einschließlich verschachtelter Elemente.

Hier ist ein komplexeres Beispiel:

python
# Verschachtelte Struktur mit mehreren Ebenen
data1 = {
    "students": [
        {"name": "Alice", "grades": [85, 90, 92]},
        {"name": "Bob", "grades": [88, 91, 87]}
    ],
    "class": "Python 101"
}
 
data2 = {
    "students": [
        {"name": "Alice", "grades": [85, 90, 92]},
        {"name": "Bob", "grades": [88, 91, 87]}
    ],
    "class": "Python 101"
}
 
print(data1 == data2)  # Output: True

Python vergleicht:

  1. Dictionary-Schlüssel und -Werte auf der obersten Ebene ("students" und "class")
  2. Die Schülerliste
  3. Jedes Schüler-Dictionary (mit den Schlüsseln "name" und "grades")
  4. Die Notenliste für jeden Schüler
  5. Jede einzelne Notenzahl

Alle Ebenen müssen übereinstimmen, damit der Vergleich True ergibt.

18.7.2) Reihenfolge ist wichtig bei Sequenzen

Bei Sequenzen (Listen und Tupeln) ist die Reihenfolge der Elemente wichtig:

python
# Reihenfolge ist wichtig in Listen
list1 = [[1, 2], [3, 4]]
list2 = [[3, 4], [1, 2]]
 
print(list1 == list2)  # Output: False (different order)
 
# Aber bei Sets spielt die Reihenfolge keine Rolle
set1 = {frozenset([1, 2]), frozenset([3, 4])}
set2 = {frozenset([3, 4]), frozenset([1, 2])}
 
print(set1 == set2)  # Output: True (sets are unordered)

18.7.3) Collections unterschiedlicher Typen vergleichen

Unterschiedliche Collection-Typen (list, tuple, set) sind niemals gleich, selbst wenn sie dieselben Elemente enthalten:

python
# Unterschiedliche Typen vergleichen
print([1, 2, 3] == (1, 2, 3))  # Output: False (list vs tuple)
print([1, 2, 3] == {1, 2, 3})  # Output: False (list vs set)
 
# Selbst mit denselben Elementen
list_version = [1, 2, 3]
tuple_version = (1, 2, 3)
set_version = {1, 2, 3}
 
print(list_version == tuple_version)  # Output: False
print(list_version == set_version)    # Output: False
print(tuple_version == set_version)   # Output: False

18.8) Flache Kopien von Listen, Dicts und Sets

Wenn Sie mit mutablen Objekten arbeiten, müssen Sie oft unabhängige Kopien erstellen, um unbeabsichtigte Änderungen zu vermeiden. Zum Beispiel, wenn Sie Daten vor der Verarbeitung sichern, Testszenarien erstellen, ohne Produktionsdaten zu beeinflussen, oder Daten an Funktionen übergeben, die das Original nicht ändern sollen. Wenn Sie verstehen, wie Pythons Kopiermechanismen funktionieren, können Sie bei Bedarf wirklich unabhängige Kopien erstellen.

Allerdings erstellen nicht alle Kopiermethoden vollständig unabhängige Kopien. Den Unterschied zwischen flachen Kopien (shallow copies) und tiefen Kopien (deep copies) zu verstehen ist entscheidend, um subtile Bugs zu vermeiden.

18.8.1) Was ist eine flache Kopie?

Eine flache Kopie (shallow copy) erstellt ein neues Objekt, erstellt aber keine Kopien der darin enthaltenen Objekte. Stattdessen enthält das neue Objekt Referenzen auf dieselben verschachtelten Objekte wie das Original.

Sehen wir uns das mit einer einfachen Liste an:

python
# Eine flache Kopie einer einfachen Liste erstellen
original = [1, 2, 3]
copy = original.copy()  # Erstellt eine flache Kopie
 
print("Original:", original)  # Output: Original: [1, 2, 3]
print("Copy:", copy)          # Output: Copy: [1, 2, 3]
 
# Sie sind unterschiedliche Objekte
print("Same object?", original is copy)  # Output: Same object? False
 
# Das Ändern der Kopie beeinflusst das Original nicht
copy.append(4)
 
print("Original:", original)  # Output: Original: [1, 2, 3]
print("Copy:", copy)          # Output: Copy: [1, 2, 3, 4]

Für einfache Listen, die immutable Objekte (wie Ganzzahlen) enthalten, funktioniert eine flache Kopie perfekt. Die Kopie ist unabhängig vom Original.

Aber was passiert bei verschachtelten Strukturen? Sehen wir uns an, wo flache Kopien ihre Grenzen zeigen:

python
# Flache Kopie mit verschachtelten Listen
original = [[1, 2], [3, 4]]
copy = original.copy()
 
print("Original:", original)  # Output: Original: [[1, 2], [3, 4]]
print("Copy:", copy)          # Output: Copy: [[1, 2], [3, 4]]
 
# Die äußeren Listen sind unterschiedliche Objekte
print("Same outer list?", original is copy)  # Output: Same outer list? False
 
# Aber die verschachtelten Listen sind die GLEICHEN Objekte
print("Same nested list?", original[0] is copy[0])  # Output: Same nested list? True
 
# Eine Änderung an einer verschachtelten Liste beeinflusst beide
copy[0].append(99)
 
print("Original:", original)  # Output: Original: [[1, 2, 99], [3, 4]]
print("Copy:", copy)          # Output: Copy: [[1, 2, 99], [3, 4]]

Originalliste

Verschachtelte Liste 1: 1, 2, 99

Verschachtelte Liste 2: 3, 4

Flache Kopie

18.8.2) Flache Kopien von Listen erstellen

Es gibt mehrere Möglichkeiten, eine flache Kopie einer Liste zu erstellen:

python
# Methode 1: Mit der Methode copy()
original = [[1, 2], [3, 4]]
copy1 = original.copy()
 
# Methode 2: Mit List-Slicing
copy2 = original[:]
 
# Methode 3: Mit dem Konstruktor list()
copy3 = list(original)
 
# Alle drei erstellen flache Kopien
print(copy1)  # Output: [[1, 2], [3, 4]]
print(copy2)  # Output: [[1, 2], [3, 4]]
print(copy3)  # Output: [[1, 2], [3, 4]]
 
# Äußere Liste ist unterschiedlich
print(original is copy1)  # Output: False
print(original is copy2)  # Output: False
print(original is copy3)  # Output: False
 
# Aber innere Listen werden GETEILT
print(original[0] is copy1[0])  # Output: True
print(original[0] is copy2[0])  # Output: True
print(original[0] is copy3[0])  # Output: True

18.8.3) Flache Kopien von Dictionaries erstellen

Auch Dictionaries unterstützen flaches Kopieren:

python
# Methode 1: Mit der Methode copy()
original = {"name": "Alice", "grade": 85}
copy1 = original.copy()
 
# Methode 2: Mit dem Konstruktor dict()
copy2 = dict(original)
 
# Beide erstellen flache Kopien
print(copy1)  # Output: {'name': 'Alice', 'grade': 85}
print(copy2)  # Output: {'name': 'Alice', 'grade': 85}
 
# Sie sind unterschiedliche Objekte
print(original is copy1)  # Output: False
print(original is copy2)  # Output: False
 
# Das Ändern der Kopie beeinflusst das Original nicht
copy1["grade"] = 90
 
print("Original:", original)  # Output: Original: {'name': 'Alice', 'grade': 85}
print("Copy:", copy1)         # Output: Copy: {'name': 'Alice', 'grade': 90}

Bei verschachtelten Strukturen gilt jedoch dieselbe Einschränkung flacher Kopien:

python
# Flache Kopie mit verschachteltem Dictionary
original = {
    "name": "Alice",
    "grades": [85, 90, 92]
}
 
copy = original.copy()
 
print("Original:", original)  # Output: Original: {'name': 'Alice', 'grades': [85, 90, 92]}
print("Copy:", copy)          # Output: Copy: {'name': 'Alice', 'grades': [85, 90, 92]}
 
# Die Dictionaries sind unterschiedliche Objekte
print("Same dict?", original is copy)  # Output: Same dict? False
 
# Aber die grades-Liste ist das GLEICHE Objekt
print("Same grades list?", original["grades"] is copy["grades"])  # Output: Same grades list? True
 
# Eine Änderung an der grades-Liste beeinflusst beide
copy["grades"].append(88)
 
print("Original:", original)  # Output: Original: {'name': 'Alice', 'grades': [85, 90, 92, 88]}
print("Copy:", copy)          # Output: Copy: {'name': 'Alice', 'grades': [85, 90, 92, 88]}
© 2025. Primesoft Co., Ltd.
support@primesoft.ai