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.SimpleNamespacefür Punkt-Zugriff - Verwendung von
collections.namedtuplefü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 attributeWenn 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) # 31Mehrere 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 BobWann 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.comSie 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
| Werkzeug | Veränderlich | Punkt-Zugriff | Methoden | Typhinweise | Am besten geeignet für |
|---|---|---|---|---|---|
dict | Ja | Nein (["key"]) | Nein | Nein | Dynamische / unbekannte Felder |
SimpleNamespace | Ja | Ja | Nein | Nein | Ad-hoc-Konfiguration, Test-Fixtures |
namedtuple | Nein | Ja | Nein | Teilweise | Unveränderliche Datensätze, kleine Daten |
class | Ja | Ja | Ja | Via Annotationen | OOP mit Verhalten |
@dataclass | Ja* | Ja | Ja | Ja | Strukturierte 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
namedtuplefü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
- Python-Variablen — wie Variablen funktionieren und Benennungsregeln
- Variablennamen — Namenskonventionen und bewährte Praktiken
- Globale Variablen — Variablen auf Modulebene und das Schlüsselwort
global - Python-Klassen und Objekte — vollständige OOP-Erklärung
- Python Dataclasses — tiefgehender Einblick in
@dataclass - Mehrere Werte zuweisen — Entpacken und Mehrfachzuweisung