Python-Kurs / Instanzattribute und Klassenattribute richtig unterscheiden

Instanzattribute speichern Daten eines einzelnen Objekts; Klassenattribute können über die Klasse gemeinsame Werte bereitstellen. Erzeugen Sie eine veränderliche Liste bei der Initialisierung jeder Instanz, wenn die Objekte unabhängige Einträge haben sollen.

Den Speicherort eines Attributs nachvollziehen

Eine Zuweisung wie self.items = [] im Initialisierer legt bei jedem Aufruf eine neue Liste an der betreffenden Instanz ab. Eine Liste items = [] unmittelbar im Klassenkörper wird dagegen beim Anlegen der Klasse erzeugt. Wenn mehrere Instanzen keine eigene gleichnamige Eigenschaft haben, greifen sie auf diese gemeinsame Klassenliste zu.

Unterscheiden Sie Abfragen, Zuweisungen und Änderungen am gefundenen Objekt. a.items.append(1) ändert die über den Attributzugriff gefundene Liste; es legt nicht automatisch eine eigene Liste an a an. Eine gewöhnliche Zuweisung a.items = [] kann dagegen ein Instanzattribut erzeugen, das die gleichnamige Klassenliste für dieses Objekt überdeckt. Die Einführung zu Klassen- und Instanzvariablen beschreibt diesen Unterschied.

Ein unveränderlicher Standard wie eine Währungsbeschriftung kann sinnvoll an der Klasse stehen. Daraus folgt nicht, dass jede Instanz diesen Wert immer benutzen muss: Ein eigenes gleichnamiges Instanzattribut kann den gemeinsamen Standard verdecken. Diese Übung betrachtet normale Attribute; besondere Mechanismen wie Properties können den Zugriff zusätzlich steuern.

Unabhängige Einträge und eine eigene Währungsbeschriftung testen

Speichern Sie den folgenden vollständigen Code als attributes.py in UTF-8 im Übungsordner. Starten Sie python attributes.py mit dem Projektinterpreter. Unter macOS oder Ubuntu kann außerhalb einer aktivierten Umgebung python3 passen. Es sind keine Zusatzpakete und keine externen Dateien nötig.

class Ledger:
    currency = "KRW"

    def __init__(self, owner):
        self.owner = owner
        self.items = []

    def add(self, amount):
        self.items.append(amount)

a = Ledger("A")
b = Ledger("B")
a.add(3500)
print(a.items, b.items)
b.currency = "USD"
print(a.currency, b.currency, Ledger.currency)

Erwartete Ausgabe:

[3500] []
KRW USD KRW

a und b erhalten je eine neue Liste. Nach a.add(3500) enthält nur a.items den Betrag; b.items bleibt leer. Die Methode fügt den Wert hinzu, prüft aber noch keine Zahlenart oder Betragsregel. Der Zweck dieser Übung ist die Verteilung des Zustands.

Vor der Zuweisung an b.currency verwenden beide Objekte den Klassenstandard KRW. Danach hat b einen eigenen Wert USD, während a und Ledger.currency weiterhin KRW liefern. Dies ändert nur eine Beschriftung und führt keine Umrechnung der gespeicherten Beträge aus. Die Grundlagen von self und Initialisierung stehen in der Übung zu Klassen und Instanzen.

Absichtliche und versehentliche gemeinsame Listen vergleichen

Lassen Sie die erste Datei unverändert. Speichern Sie den folgenden zweiten vollständigen Vergleich als shared_attributes.py und starten Sie python shared_attributes.py aus demselben Ordner:

class Shared:
    items = []
class Separate:
    def __init__(self):
        self.items = []
a, b = Shared(), Shared()
a.items.append(1)
print(a.items, b.items)
x, y = Separate(), Separate()
x.items.append(1)
print(x.items, y.items)

Erwartete Ausgabe bei einem frischen Skriptstart:

[1] [1]
[1] []

Bei Shared verweist der Zugriff über beide Instanzen auf die Klassenliste. Ein Anhängen über a ist daher auch über b sichtbar. Bei Separate entsteht pro Initialisierung eine eigene Liste; x und y teilen deren äußeres Listenobjekt nicht.

Auch eine spätere Änderung von Ledger.currency sollte anhand der tatsächlichen Bindungen beurteilt werden. Wird der Klassenwert nach der ersten Übung beispielsweise auf EUR gesetzt, findet a.currency diesen neuen Standard. b.currency bleibt dagegen USD, weil das eigene Attribut weiterhin vorhanden ist. Nach del b.currency würde auch b wieder auf den Klassenstandard zurückfallen. Das Löschen des eigenen Attributs löscht nicht das Klassenattribut.

Wenn ein Initialisierer eine externe Liste direkt als self.items übernimmt, wird diese weiterhin mit dem Aufrufer geteilt. Eine neue Instanz allein verspricht daher noch keine vollständig unabhängigen Daten. Eine flache Kopie trennt das äußere Objekt, kann aber innere veränderliche Objekte weiter gemeinsam referenzieren. Die Übung zu Alias und Kopien erläutert diese zusätzliche Ebene.

Gemeinsamen Zustand reproduzieren und gezielt beheben

  1. Ergänzen Sie im Klassenkörper von Ledger unter currency eine Zeile items = [] mit derselben Einrückung. Entfernen Sie nur die Zuweisung self.items = [] aus dem Initialisierer; dessen self.owner = owner bleibt erhalten.
  2. Starten Sie frisch. Nach dem Anhängen über a zeigt die erste Ausgabe nun [3500] [3500]. Beide Zugriffe verwenden die Klassenliste.
  3. Entfernen Sie das Klassenattribut items und setzen Sie self.items = [] wieder in den Initialisierer. Nach einem frischen Start muss die erste Ausgabe wieder [3500] [] sein.
  4. Prüfen Sie die Identität ausdrücklich mit a.items is b.items: Sie ist in der gemeinsamen Variante wahr und in der ursprünglichen unabhängigen Variante falsch. Vergleichen Sie danach auch die ursprüngliche Währungszeile erneut.

Gemeinsame Sammlungen können eine bewusste Konstruktion sein, erfordern dann aber klare Zuständigkeit, Lebensdauer und Regeln für gleichzeitige Zugriffe. Verwenden Sie eine versehentlich geteilte Klassenliste nicht als versteckten allgemeinen Datenspeicher. Wenn ein Klassenstandard samt Überdeckung die gewünschte Konfiguration schwer verständlich macht, kann ein ausdrücklicher Initialisierungsparameter pro Instanz die einfachere Schnittstelle sein.