W3docs

Python-Variablen gruppieren

Variablen in Python mit Klassen, Dataclasses, Named Tuples, SimpleNamespace und Dicts gruppieren — mit ausführbaren Beispielen.

Wenn ein Programm mehrere zusammengehörige Daten verwalten muss — zum Beispiel Name, Alter und E-Mail-Adresse eines Benutzers —, wird die Verwaltung einzelner, unverbundener Variablen schnell unübersichtlich. Python bietet verschiedene Werkzeuge, um Variablen unter einem gemeinsamen Namen zu gruppieren, sodass sie zusammenbleiben und geordnet sind. Dieses Kapitel erklärt die gängigsten Ansätze, wann man welchen einsetzt und welche Kompromisse damit verbunden sind.

Behandelte Themen:

  • Warum das Gruppieren von Variablen wichtig ist
  • Verwendung eines einfachen Dictionarys
  • Verwendung von types.SimpleNamespace für Punkt-Zugriff
  • Verwendung von collections.namedtuple für leichtgewichtige, unveränderliche Datensätze
  • Verwendung einer Klasse mit __init__
  • Verwendung von @dataclass (Python 3.7+) für die übersichtlichste Syntax
  • Das richtige Werkzeug wählen

Warum Variablen gruppieren?

Angenommen, Sie schreiben ein Skript, das Benutzerkonten verarbeitet. Ohne Gruppierung würden Sie möglicherweise schreiben:

user_name = "Alice"
user_age = 30
user_email = "[email protected]"

Das funktioniert für einen einzelnen Benutzer, stößt aber sofort an Grenzen, wenn Sie zwei Benutzer benötigen oder Daten an eine Funktion übergeben:

def greet(name, age, email):
    print(f"Hello {name}, age {age} ({email})")

greet(user_name, user_age, user_email)

Drei separate Argumente müssen überall synchron gehalten werden. Durch Gruppierung lässt sich dieses Problem lösen, indem die Daten gebündelt werden:

user = {"name": "Alice", "age": 30, "email": "[email protected]"}

def greet(user):
    print(f"Hello {user['name']}, age {user['age']} ({user['email']})")

greet(user)

Die Funktionssignatur hat jetzt nur noch einen Parameter statt drei, und das Hinzufügen eines neuen Felds betrifft nur das Dictionary.

Verwendung eines Dictionarys

Ein Python-Dictionary ist die einfachste Möglichkeit, benannte Variablen zu gruppieren. Schlüssel sind Strings; Werte können jeden Typ haben.

point = {"x": 10, "y": 20, "label": "origin"}

print(point["x"])      # 10
print(point["label"])  # origin

# Update a field
point["x"] = 15
print(point)
# {'x': 15, 'y': 20, 'label': 'origin'}

Wann verwenden: Schnelle einmalige Gruppierung, JSON-Daten, Situationen, in denen die Menge der Felder nicht im Voraus festgelegt ist.

Nachteile: Der Zugriff auf Felder erfolgt über String-Schlüssel (point["x"]), was ausführlicher als die Punkt-Notation ist und keine IDE-Autovervollständigung bietet.

Verwendung von types.SimpleNamespace

SimpleNamespace ist ein schlanker Wrapper, der Punkt-Zugriff auf einen ad-hoc-Namensraum ermöglicht, ohne eine Klasse schreiben zu müssen.

from types import SimpleNamespace

point = SimpleNamespace(x=10, y=20, label="origin")

print(point.x)      # 10
print(point.label)  # origin

# Update a field
point.x = 15
print(point)
# namespace(x=15, y=20, label='origin')

SimpleNamespace-Objekte sind veränderlich — Sie können Attribute jederzeit hinzufügen, ändern oder löschen:

from types import SimpleNamespace

config = SimpleNamespace(debug=False, timeout=30)
config.debug = True     # update
config.retries = 3      # add new attribute
del config.timeout      # remove

print(vars(config))
# {'debug': True, 'retries': 3}

Wann verwenden: Als Ersatz für ein Dictionary, wenn Sie Punkt-Zugriff wünschen, aber keine Methoden oder Typprüfung benötigen. Gut geeignet für Test-Fixtures und einfache Konfigurationsobjekte.

Verwendung von collections.namedtuple

Ein namedtuple ist ein unveränderlicher, leichtgewichtiger Datensatz. Es verhält sich wie ein reguläres Tupel, erlaubt aber auch den Zugriff auf Felder per Name sowie per Index.

from collections import namedtuple

# Define the type once
Point = namedtuple("Point", ["x", "y"])

# Create an instance
p = Point(x=10, y=20)

print(p.x)    # 10
print(p.y)    # 20
print(p[0])   # 10 — index access still works
print(p)      # Point(x=10, y=20)

Da namedtuple-Instanzen unveränderlich sind, kann ein Feld nach der Erstellung nicht geändert werden:

from collections import namedtuple

Color = namedtuple("Color", ["red", "green", "blue"])
white = Color(255, 255, 255)

# white.red = 0  # AttributeError: can't set attribute

Wenn Sie eine geänderte Kopie benötigen, verwenden Sie die Methode _replace() — sie gibt eine neue Instanz zurück:

from collections import namedtuple

Color = namedtuple("Color", ["red", "green", "blue"])
white = Color(255, 255, 255)

grey = white._replace(red=128, green=128, blue=128)
print(grey)
# Color(red=128, green=128, blue=128)

Wann verwenden: Unveränderliche Datensätze, bei denen Feldnamen wichtig sind — Koordinaten, RGB-Farben, Datenbankzeilen. Geringerer Speicherbedarf als bei einer vollständigen Klasse.

Verwendung einer Klasse

Für gruppierte Variablen, die auch Verhalten (Methoden) benötigen, definieren Sie eine Klasse mit einer __init__-Methode:

class User:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

    def greet(self):
        return f"Hello, I am {self.name} and I am {self.age} years old."

alice = User("Alice", 30, "[email protected]")
print(alice.name)     # Alice
print(alice.greet())  # Hello, I am Alice and I am 30 years old.

# Update a field
alice.age = 31
print(alice.age)      # 31

Mehrere Instanzen bleiben unabhängig voneinander — jede enthält ihre eigene Kopie von name, age und email:

class User:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

alice = User("Alice", 30, "[email protected]")
bob   = User("Bob",   25, "[email protected]")

print(alice.name, bob.name)   # Alice Bob

Wann verwenden: Immer dann, wenn die gruppierten Daten auch Methoden, Validierungslogik oder Vererbung benötigen. Klassen sind die Grundlage der objektorientierten Programmierung in Python — eine vollständige Erklärung finden Sie unter Python-Klassen und Objekte.

Verwendung von @dataclass (Python 3.7+)

Der @dataclass-Dekorator generiert automatisch __init__, __repr__ und __eq__ aus annotierten Klassenfeldern und beseitigt so den größten Teil des Boilerplate-Codes:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float
    label: str = "unnamed"

p = Point(x=3.0, y=4.0)
print(p)           # Point(x=3.0, y=4.0, label='unnamed')
print(p.label)     # unnamed

p.label = "A"
print(p)           # Point(x=3.0, y=4.0, label='A')

Felder mit einem Standardwert müssen nach Feldern ohne Standardwert stehen (dieselbe Regel wie bei regulären Funktionsargumenten).

Unveränderliche Dataclass mit frozen=True

Übergeben Sie frozen=True, um zu verhindern, dass ein Feld nach der Erstellung geändert wird — ähnlich im Verhalten wie ein namedtuple, aber mit allen Klassenfähigkeiten:

from dataclasses import dataclass

@dataclass(frozen=True)
class RGB:
    red: int
    green: int
    blue: int

white = RGB(255, 255, 255)
print(white)
# RGB(red=255, green=255, blue=255)

# white.red = 0  # FrozenInstanceError: cannot assign to field 'red'

Mehrere Datensätze in einer Liste gruppieren

Dataclasses lassen sich hervorragend mit Listen kombinieren, wenn Sie eine Sammlung von Datensätzen benötigen:

from dataclasses import dataclass
from typing import List

@dataclass
class Product:
    name: str
    price: float
    in_stock: bool = True

inventory: List[Product] = [
    Product("Widget", 9.99),
    Product("Gadget", 24.99),
    Product("Doohickey", 4.50, in_stock=False),
]

for item in inventory:
    status = "available" if item.in_stock else "out of stock"
    print(f"{item.name}: ${item.price:.2f} ({status})")

Ausgabe:

Widget: $9.99 (available)
Gadget: $24.99 (available)
Doohickey: $4.50 (out of stock)

Den vollständigen Funktionsumfang von Dataclasses, einschließlich field(), __post_init__ und Vererbung, finden Sie unter Python Dataclasses.

Variablen mit Klassenattributen gruppieren

Manchmal möchten Sie gemeinsam genutzte Konstanten einer Gruppe zuordnen, anstatt instanzspezifische Daten zu verwenden. Klassenattribute (direkt im Klassenkörper, außerhalb von __init__ definiert) werden von allen Instanzen gemeinsam genutzt:

class AppConfig:
    MAX_RETRIES = 3
    TIMEOUT = 30
    BASE_URL = "https://api.example.com"

print(AppConfig.MAX_RETRIES)  # 3
print(AppConfig.BASE_URL)     # https://api.example.com

Sie müssen AppConfig nicht instanziieren, um auf seine Attribute zuzugreifen — betrachten Sie die Klasse selbst als Namensraum für zusammengehörige Konstanten. Dies ist ein leichtgewichtiges Muster für Konfigurationsgruppen. Eine ausführlichere Diskussion von Klassenattributen versus Instanzattributen finden Sie unter Python-Klassen und Objekte.

Das richtige Werkzeug wählen

WerkzeugVeränderlichPunkt-ZugriffMethodenTyphinweiseAm besten geeignet für
dictJaNein (["key"])NeinNeinDynamische / unbekannte Felder
SimpleNamespaceJaJaNeinNeinAd-hoc-Konfiguration, Test-Fixtures
namedtupleNeinJaNeinTeilweiseUnveränderliche Datensätze, kleine Daten
classJaJaJaVia AnnotationenOOP mit Verhalten
@dataclassJa*JaJaJaStrukturierte Datensätze mit Methoden

*frozen=True macht eine Dataclass unveränderlich.

Faustregel:

  • Verwenden Sie ein dict, wenn die Struktur nicht im Voraus bekannt ist.
  • Verwenden Sie SimpleNamespace, wenn Sie Punkt-Zugriff ohne Klassendefinition möchten.
  • Verwenden Sie namedtuple für einfache, unveränderliche Datensätze (Koordinaten, Farben, Zeilen).
  • Verwenden Sie eine reguläre class, wenn Sie Methoden und vollständige OOP benötigen.
  • Verwenden Sie @dataclass, wenn Sie einen strukturierten Datensatz mit optionalen Methoden benötigen — es bietet am meisten mit dem wenigsten Boilerplate-Code.

Verwandte Themen

Übungen

Übung
In Python, what are the main reasons for grouping variables into classes?
In Python, what are the main reasons for grouping variables into classes?
Was this page helpful?