Python @staticmethod und @classmethod
Erfahre, wie @staticmethod und @classmethod in Python funktionieren, wann du sie einsetzt und wie du Factory-Methoden und Hilfsfunktionen schreibst.
Python gibt jeder Methode innerhalb einer Klasse einen von drei Bindungsstilen: Sie kann an eine Instanz, an die Klasse selbst oder an keines von beidem gebunden sein. Die Dekoratoren @classmethod und @staticmethod steuern diese letzten beiden Stile.
Dieses Kapitel behandelt:
- Die drei Methodentypen und ihre Unterschiede
@staticmethod— eine einfache Funktion, die in einem Klassen-Namensraum gespeichert ist@classmethod— eine Methode, die die Klasse als erstes Argument erhält- Factory-Methoden: der häufigste Praxiseinsatz von
@classmethod - Alternative Konstruktoren und ihr Zusammenspiel mit Vererbung
- Wann du
@staticmethodvs.@classmethodvs. eine Funktion auf Modulebene wählen solltest - Häufige Fallstricke
Stelle vor dem Lesen sicher, dass du mit Python-Klassen und -Objekten und Python-Vererbung vertraut bist. Für berechneten Attributzugriff, siehe @property. Für eine tiefgehende Erklärung, wie Dekoratoren im Allgemeinen funktionieren, siehe Python-Dekoratoren.
Die drei Methodentypen
Bevor wir uns jeden Dekorator einzeln ansehen, hier ein direkter Vergleich:
| Instanzmethode | @classmethod | @staticmethod | |
|---|---|---|---|
| Erster Parameter | self (die Instanz) | cls (die Klasse) | keiner |
| Erhält die Instanz? | Ja | Nein | Nein |
| Erhält die Klasse? | Über type(self) | Ja (direkt) | Nein |
| Aufruf auf einer Instanz | Ja | Ja | Ja |
| Aufruf auf der Klasse | Ja (aber self fehlt) | Ja | Ja |
| Typischer Einsatz | Instanzdaten verarbeiten | Factory-Methoden, klassenweiter Zustand | Hilfs-/Utility-Funktionen |
class Demo:
def instance_method(self):
return f"instance method — self is {self}"
@classmethod
def class_method(cls):
return f"class method — cls is {cls}"
@staticmethod
def static_method():
return "static method — no self, no cls"
d = Demo()
print(d.instance_method()) # instance method — self is <__main__.Demo object at 0x...>
print(d.class_method()) # class method — cls is <class '__main__.Demo'>
print(d.static_method()) # static method — no self, no cls
# All three can also be called directly on the class:
print(Demo.class_method()) # class method — cls is <class '__main__.Demo'>
print(Demo.static_method()) # static method — no self, no cls@staticmethod
Eine statische Methode ist die einfachste der drei. Sie ist eine gewöhnliche Funktion, die zufällig im Namensraum einer Klasse lebt. Python übergibt self oder cls nicht automatisch.
class MathUtils:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_even(n):
return n % 2 == 0
print(MathUtils.add(3, 4)) # 7
print(MathUtils.is_even(10)) # TrueWann @staticmethod verwenden
Verwende @staticmethod, wenn eine Hilfsfunktion logisch zu einer Klasse gehört — aus Gründen der Klarheit, Gruppierung oder Namensgebung —, aber weder den Instanz- noch den Klassenzustand lesen oder ändern muss:
- Validierungs-Hilfsfunktionen, die vor dem Erstellen eines Objekts aufgerufen werden.
- Reine Konvertierungs- oder Berechnungsfunktionen, die nur im Kontext einer Klasse sinnvoll sind.
- Utility-Funktionen, die von mehreren Methoden derselben Klasse verwendet werden, aber sonst nirgendwo.
class Temperature:
def __init__(self, celsius):
if not Temperature._is_valid(celsius):
raise ValueError(f"Temperature {celsius} °C is below absolute zero")
self.celsius = celsius
@staticmethod
def _is_valid(celsius):
return celsius >= -273.15
@staticmethod
def celsius_to_fahrenheit(celsius):
return celsius * 9 / 5 + 32
t = Temperature(100)
print(Temperature.celsius_to_fahrenheit(100)) # 212.0
print(Temperature._is_valid(-300)) # FalseBeachte, dass _is_valid mit _ beginnt, um anzuzeigen, dass es intern für die Klasse gedacht ist. Aufrufer, die nur Temperature-Objekte benötigen, sehen es nie — sie erhalten lediglich einen ValueError, wenn sie einen unmöglichen Wert übergeben.
@staticmethod vs. eine Funktion auf Modulebene
Eine Funktion auf Modulebene und ein @staticmethod verhalten sich nahezu identisch. Der Unterschied liegt darin, wo die Funktion lebt:
- Wenn die Funktion nur für
Temperaturerelevant ist (oder ausschließlich ausTemperatureheraus aufgerufen wird), platziere sie als@staticmethodin der Klasse. - Wenn es sich um eine allgemeine Hilfsfunktion handelt, die im gesamten Modul verwendet wird, platziere sie auf Modulebene.
Es gibt keinen Leistungsunterschied. Dies ist eine rein organisatorische Entscheidung.
@classmethod
Eine Klassenmethode erhält die Klasse als erstes Argument (konventionell cls genannt — aber genau wie self ist der Name eine Konvention, kein Schlüsselwort). Da sie einen Verweis auf die Klasse hat, kann sie:
- Attribute auf Klassenebene lesen oder ändern.
- Neue Instanzen der Klasse erstellen und zurückgeben (Factory-Methoden).
- Korrekt mit Unterklassen arbeiten (polymorphe Factories).
class Counter:
_count = 0 # class-level attribute
def __init__(self):
Counter._count += 1
@classmethod
def get_count(cls):
return cls._count
@classmethod
def reset(cls):
cls._count = 0
Counter()
Counter()
Counter()
print(Counter.get_count()) # 3
Counter.reset()
print(Counter.get_count()) # 0Factory-Methoden — der wichtigste Anwendungsfall
Der häufigste und wertvollste Einsatz von @classmethod ist als Factory-Methode (auch alternativer Konstruktor genannt). Eine Factory-Methode erstellt Instanzen aus verschiedenen Arten von Eingaben, ohne __init__ mit bedingter Logik zu überfrachten.
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"Date({self.year}, {self.month}, {self.day})"
@classmethod
def from_string(cls, date_string):
"""Create a Date from an ISO 8601 string, e.g. '2024-03-15'."""
year, month, day = (int(p) for p in date_string.split("-"))
return cls(year, month, day)
@classmethod
def from_tuple(cls, date_tuple):
"""Create a Date from a (year, month, day) tuple."""
return cls(*date_tuple)
d1 = Date(2024, 3, 15)
d2 = Date.from_string("2024-03-15")
d3 = Date.from_tuple((2024, 3, 15))
print(d1) # Date(2024, 3, 15)
print(d2) # Date(2024, 3, 15)
print(d3) # Date(2024, 3, 15)__init__ bleibt einfach — es speichert nur drei Integer. Die Klassenmethoden übernehmen die Konvertierungslogik. Das ist sauberer als ein einziges __init__ mit mehreren optionalen Parametern und if/elif-Verzweigungen.
Warum cls bei der Vererbung wichtig ist
Wenn eine Factory-Klassenmethode cls(...) aufruft, anstatt den Klassennamen fest einzucodieren, erstellt sie eine Instanz von der Klasse, auf der die Methode aufgerufen wurde — auch einer Unterklasse. Deshalb solltest du innerhalb eines @classmethod immer cls(...) gegenüber ClassName(...) bevorzugen.
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"{type(self).__name__}({self.year}, {self.month}, {self.day})"
@classmethod
def from_string(cls, date_string):
year, month, day = (int(p) for p in date_string.split("-"))
return cls(year, month, day) # uses cls, not Date
class DateTime(Date):
pass # inherits from_string
dt = DateTime.from_string("2024-03-15")
print(dt) # DateTime(2024, 3, 15) — correct subclass
print(type(dt)) # <class '__main__.DateTime'>Wenn from_string return Date(year, month, day) fest eincodiert hätte, würde der Aufruf DateTime.from_string(...) ein Date zurückgeben, kein DateTime — was den Vererbungsvertrag still und leise bricht.
Zustand auf Klassenebene verändern
Klassenmethoden können auch als benannte Konstruktoren mit Seiteneffekten fungieren oder Klassenvariablen manipulieren, die gemeinsamen Zustand verfolgen:
class Registry:
_instances = []
def __init__(self, name):
self.name = name
Registry._instances.append(self)
@classmethod
def all(cls):
return list(cls._instances)
@classmethod
def clear(cls):
cls._instances.clear()
Registry("alice")
Registry("bob")
Registry("carol")
print([r.name for r in Registry.all()]) # ['alice', 'bob', 'carol']
Registry.clear()
print(Registry.all()) # []Aufruf auf einer Instanz vs. der Klasse
Sowohl @staticmethod als auch @classmethod können auf einer Instanz oder der Klasse aufgerufen werden. Python verarbeitet beide Formen:
class Circle:
PI = 3.14159265
def __init__(self, radius):
self.radius = radius
def area(self):
return Circle.PI * self.radius ** 2
@classmethod
def unit_circle(cls):
"""Return a circle with radius 1."""
return cls(1)
@staticmethod
def describe():
return "A circle is a round plane figure."
c = Circle(5)
# staticmethod — callable on instance or class
print(c.describe()) # A circle is a round plane figure.
print(Circle.describe()) # A circle is a round plane figure.
# classmethod — callable on instance or class
unit = c.unit_circle()
print(unit.radius) # 1
print(Circle.unit_circle().radius) # 1Der Aufruf auf der Klasse ist in der Regel klarer — er signalisiert dem Leser, dass keine Instanzdaten beteiligt sind.
@classmethod und @staticmethod kombinieren
Eine Klassenmethode kann Validierungsarbeit an eine statische Methode delegieren, da die Klassenmethode Zugriff auf cls hat, um sie aufzurufen:
class PositiveNumber:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"PositiveNumber({self.value})"
@staticmethod
def _validate(value):
if value <= 0:
raise ValueError(f"Expected a positive number, got {value!r}")
@classmethod
def create(cls, value):
cls._validate(value)
return cls(value)
n = PositiveNumber.create(42)
print(n) # PositiveNumber(42)
try:
PositiveNumber.create(-5)
except ValueError as e:
print(e) # Expected a positive number, got -5Kurzreferenz: Welchen Dekorator soll ich verwenden?
| Situation | Empfehlung |
|---|---|
Die Methode liest oder schreibt self | Reguläre Instanzmethode |
| Die Methode erstellt eine neue Instanz | @classmethod (Factory / alternativer Konstruktor) |
| Die Methode liest oder schreibt ein Klassenattribut | @classmethod |
| Die Methode ist eine reine Hilfsfunktion, die keine Klassen- oder Instanzdaten benötigt | @staticmethod (oder Funktion auf Modulebene) |
| Die Methode validiert Eingaben vor der Konstruktion | @staticmethod |
| Die Methode soll in Unterklassen korrekt funktionieren | @classmethod (verwende cls, nicht den fest eincodierten Klassennamen) |
Häufige Fallstricke
cls in @classmethod vergessen
Wenn du den Klassennamen fest eincodierst statt cls zu verwenden, bricht die Vererbung still:
class Animal:
@classmethod
def create(cls):
return cls() # correct — returns an instance of the actual class
class Dog(Animal):
pass
print(type(Dog.create())) # <class '__main__.Dog'> — correctVerwende immer cls(...), niemals Animal(...), innerhalb einer Klassenmethode.
Auf self oder cls in einem @staticmethod zugreifen
Ein @staticmethod erhält kein implizites erstes Argument. Der Versuch, innerhalb davon auf self oder cls zu verweisen, ist ein Fehler:
class Bad:
label = "bad"
@staticmethod
def show():
# print(cls.label) # NameError: name 'cls' is not defined
print("use @classmethod if you need cls")
Bad.show() # use @classmethod if you need clsWenn du in einer vermeintlichen statischen Methode cls benötigst, wechsle zu einem @classmethod.
Die Dekoratoren verwechseln
@classmethod-Methoden müssen cls als ersten expliziten Parameter haben, und @staticmethod-Methoden müssen keinen haben. Das Vertauschen führt zu einem TypeError zum Aufrufzeitpunkt, nicht zum Definitionszeitpunkt — was überraschend sein kann:
class Broken:
@staticmethod
def forgot_cls(cls): # cls is just a regular positional argument here
return cls
# Broken.forgot_cls() # TypeError: forgot_cls() missing 1 required positional argument: 'cls'Überschreiben in Unterklassen
Beide Dekoratoren funktionieren mit super() und können überschrieben werden:
class Base:
@classmethod
def who(cls):
return f"Base.who called with cls={cls.__name__}"
class Child(Base):
@classmethod
def who(cls):
parent = super().who()
return f"Child.who — parent said: {parent}"
print(Child.who())
# Child.who — parent said: Base.who called with cls=ChildBeachte, dass cls in Base.who immer noch Child ist — weil die Methode von Child aus aufgerufen wurde.
Praxisbeispiel: Eine User-Klasse
Hier ist ein vollständiges Beispiel, das Instanzmethoden, eine Klassenmethoden-Factory und einen statischen Methoden-Validator zusammenführt:
import re
class User:
_all_users = []
def __init__(self, name, email):
User._validate_email(email)
self.name = name
self.email = email
User._all_users.append(self)
def __repr__(self):
return f"User(name={self.name!r}, email={self.email!r})"
# --- instance method ---
def greet(self):
return f"Hello, my name is {self.name}."
# --- factory / alternative constructor ---
@classmethod
def from_dict(cls, data):
"""Create a User from a dict like {'name': 'Alice', 'email': '[email protected]'}."""
return cls(data["name"], data["email"])
# --- class-level query ---
@classmethod
def count(cls):
return len(cls._all_users)
# --- pure helper, no instance or class data needed ---
@staticmethod
def _validate_email(email):
pattern = r"^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$"
if not re.match(pattern, email):
raise ValueError(f"Invalid email address: {email!r}")
# Create via normal constructor
u1 = User("Alice", "[email protected]")
# Create via factory
u2 = User.from_dict({"name": "Bob", "email": "[email protected]"})
print(u1.greet()) # Hello, my name is Alice.
print(u2.greet()) # Hello, my name is Bob.
print(User.count()) # 2
try:
User("Carol", "not-an-email")
except ValueError as e:
print(e) # Invalid email address: 'not-an-email'Dieses Muster — __init__ für die normale Konstruktion, @classmethod für alternative Konstruktoren, @staticmethod für Hilfsfunktionen — taucht in der gesamten Python-Standardbibliothek auf (siehe datetime.date.today(), datetime.date.fromisoformat(), int.from_bytes()).
Zusammenfassung
- Eine Instanzmethode erhält
selfund hat vollen Zugriff auf den Zustand des Objekts. - Ein
@classmethoderhältcls— die Klasse selbst — statt einer Instanz. Verwende ihn für Factory-Methoden und alles, was auf dem Zustand der Klasse operiert. Verwende darin immercls(...), damit Unterklassen korrekt funktionieren. - Ein
@staticmethoderhält wederselfnochcls. Verwende ihn für reine Utility-Logik, die in den Klassen-Namensraum gehört, aber keine Objekt- oder Klassendaten benötigt.
Für berechnete Attribute, die wie ein normaler Attributzugriff aussehen, siehe @property. Für den vollständigen Dekoratormechanismus, der alle drei möglich macht, siehe Python-Dekoratoren.