W3docs

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 @staticmethod vs. @classmethod vs. 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 Parameterself (die Instanz)cls (die Klasse)keiner
Erhält die Instanz?JaNeinNein
Erhält die Klasse?Über type(self)Ja (direkt)Nein
Aufruf auf einer InstanzJaJaJa
Aufruf auf der KlasseJa (aber self fehlt)JaJa
Typischer EinsatzInstanzdaten verarbeitenFactory-Methoden, klassenweiter ZustandHilfs-/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)) # True

Wann @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))             # False

Beachte, 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 Temperature relevant ist (oder ausschließlich aus Temperature heraus aufgerufen wird), platziere sie als @staticmethod in 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())  # 0

Factory-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)  # 1

Der 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 -5

Kurzreferenz: Welchen Dekorator soll ich verwenden?

SituationEmpfehlung
Die Methode liest oder schreibt selfRegulä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'>  — correct

Verwende 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 cls

Wenn 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=Child

Beachte, 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 self und hat vollen Zugriff auf den Zustand des Objekts.
  • Ein @classmethod erhält cls — die Klasse selbst — statt einer Instanz. Verwende ihn für Factory-Methoden und alles, was auf dem Zustand der Klasse operiert. Verwende darin immer cls(...), damit Unterklassen korrekt funktionieren.
  • Ein @staticmethod erhält weder self noch cls. 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.

Was this page helpful?