Wenn man in Python mit Klassen arbeitet, stößt man auf den @classmethod Dekorator. Dieser hat einen klar definierten Zweck, der sich von @staticmethod und der normalen Instanzmethode (Instance Method) unterscheidet.

In diesem Artikel werden wir genau herausfinden, was @classmethod ist und in welchen Situationen es im Vergleich zu @staticmethod und Instanzmethoden verwendet werden sollte.


1. Drei Arten von Methoden: Instanz vs. Klasse vs. Static



In Python-Klassen gibt es grundsätzlich drei Arten von Methoden. Der größte Unterschied besteht darin, was der erste Parameter der Methode ist.

class MyClass:
    # 1. Instanzmethode (Instance Method)
    def instance_method(self, arg1):
        # 'self' erhält die Instanz der Klasse.
        # Es kann auf Instanzattribute (self.x) zugegriffen und diese können geändert werden.
        print(f"Instanzmethode wurde mit {self} und {arg1} aufgerufen")

    # 2. Klassenmethode (Class Method)
    @classmethod
    def class_method(cls, arg1):
        # 'cls' erhält die Klasse selbst (MyClass).
        # Wird hauptsächlich verwendet, um auf Klassenattribute zuzugreifen oder 
        # eine Instanz zu erzeugen, als 'Fabrik'.
        print(f"Klassenmethode wurde mit {cls} und {arg1} aufgerufen")

    # 3. Statische Methode (Static Method)
    @staticmethod
    def static_method(arg1):
        # 'self' oder 'cls' werden nicht empfangen.
        # Eine Utility-Funktion, die unabhängig vom Zustand der Klasse oder der Instanz ist.
        # Diese Funktion gehört nur logisch zur Klasse.
        print(f"Statische Methode wurde mit {arg1} aufgerufen")

# Beispiel für Methodenaufruf
obj = MyClass()

# Instanzmethode (durch die Instanz aufgerufen)
obj.instance_method("Hallo") 

# Klassenmethode (kann sowohl über die Klasse als auch über die Instanz aufgerufen werden)
MyClass.class_method("Welt")
obj.class_method("Welt") # Dies ist zwar möglich, aber es ist klarer, über die Klasse zu rufen.

# Statische Methode (kann sowohl über die Klasse als auch über die Instanz aufgerufen werden)
MyClass.static_method("Python")
obj.static_method("Python")

Vergleich auf einen Blick

Typ Erster Parameter Zugriffsziel Hauptverwendung
Instanzmethode self (Instanz) Instanzattribute (self.name) Zugriff auf oder Manipulation des Zustands des Objekts
Klassenmethode cls (Klasse) Klassenattribute (cls.count) Alternative Konstruktor (Fabrik)
Statische Methode Keine Keine Utility-Funktion, die logisch zur Klasse gehört

2. Hauptverwendung von @classmethod: Fabrikmethode

Der wichtigste und allgemeinste Anwendungsfall von @classmethod ist die Definition eines "Alternativen Konstruktors (Alternative Constructor)" oder einer "Fabrikmethode (Factory Method)".

Der Standardkonstruktor __init__ erhält normalerweise klar definierte Parameter zur Initialisierung des Objekts. Manchmal ist es jedoch notwendig, Objekte auf andere Weise (z.B. über String-Parsing, Wörterbücher, bestimmte berechnete Werte) zu erstellen.

Durch die Verwendung von @classmethod kann man einen weiteren 'Einstiegspunkt' schaffen, der eine Klasseninstanz zurückgibt, neben __init__.

classmethod Diagramm

Beispiel: Erstellen eines Person Objekts mit einem Datum-String

Angenommen, die Person Klasse erhält name und age. Wie könnte man ein Objekt auch aus einem String im Format 'Name-Geburtsjahr' erstellen?

import datetime

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

    def greet(self):
        print(f"Hallo, ich heiße {self.name} und bin {self.age} Jahre alt.")

    @classmethod
    def from_birth_year(cls, name, birth_year):
        """
        Nimmt das Geburtsjahr und berechnet das Alter, um eine Person-Instanz zu erstellen.
        """
        current_year = datetime.datetime.now().year
        age = current_year - birth_year

        # 'cls' verweist auf die Person-Klasse selbst.
        # return Person(name, age) ist dasselbe, aber,
        # durch die Verwendung von 'cls' hat man Vorteile bei Vererbung. (wie weiter unten erklärt)
        return cls(name, age)

# Verwendung des Standardkonstruktors
person1 = Person("Alice", 30)
person1.greet()

# Verwendung des alternativen Konstruktors von @classmethod
person2 = Person.from_birth_year("MadHatter", 1995)
person2.greet()

Ausgabe:

Hallo, ich heiße Alice und bin 30 Jahre alt.
Hallo, ich heiße MadHatter und bin 30 Jahre alt. (muss für das Jahr 2025 validiert werden)

Im obigen Beispiel greift die Methode from_birth_year über den Parameter cls auf die Klasse Person zu und ruft cls(name, age) auf, was gleichbedeutend mit dem Aufruf von Person(name, age) ist und eine neue Person Instanz zurückgibt.


3. @classmethod und Vererbung



„Warum solltest du cls(...) verwenden, wenn du einfach Person(...) aufrufen kannst?“

Die wahre Kraft von @classmethod zeigt sich in der Vererbung. cls verweist auf _genau die Klasse_, die derzeit die Methode aufgerufen hat.

Angenommen, wir erstellen eine Student Klasse, die von Person erbt.

class Student(Person):
    def __init__(self, name, age, major):
        super().__init__(name, age) # Ruft __init__ der Elternklasse auf
        self.major = major

    def greet(self):
        # Methodenüberschreibung
        print(f"Hallo, ich bin {self.major} Major {self.name} und bin {self.age} Jahre alt.")

# Ruft die @classmethod der Elternklasse von Student auf.
student1 = Student.from_birth_year("Jhon", 2005, "Informatik") 
# Oh, ein TypeError tritt auf!

# Wenn man Student.from_birth_year aufruft, 
# wird Person.from_birth_year ausgeführt, und dabei ist 'cls' 'Student'.
# Mit anderen Worten, return cls(name, age) wird zu return Student(name, age).
# Aber __init__ von Student benötigt das zusätzliche Argument 'major'.

Der oben stehende Code führt zu einem Fehler, da Student’s __init__ das major Argument benötigt. Aber die Kernidee ist, dass cls auf Student zeigt und nicht auf Person.

Wenn from_birth_year eine @staticmethod gewesen wäre oder intern direkt Person(name, age) verwendet worden wäre, würde der Aufruf von Student.from_birth_year ein Person Objekt anstelle eines Student Objekts zurückgeben.

Durch @classmethod wird gewährleistet, dass selbst wenn die Kindklasse aufgerufen wird, es die korrekte Instanz der Kindklasse erzeugt. (Natürlich, wenn das Signatur des Kindklasse __init__ sich ändert, sollte auch from_birth_year überschrieben werden.)


4. Zusammenfassung

Zusammengefasst lässt sich sagen, dass @classmethod folgende Punkte hat:

  1. @classmethod erhält cls (die Klasse selbst) als ersten Parameter.

  2. Die Hauptverwendung ist die 'Alternative Konstruktor (Fabrikmethode)'. Es wird verwendet, um verschiedene Möglichkeiten zur Erstellung von Objekten anzubieten, abgesehen von __init__.

  3. Es ist mächtig in Verbindung mit Vererbung. Selbst wenn es von Kindklassen aufgerufen wird, zeigt cls auf die korrekte Kindklasse, um die Instanz dieser Klasse zu erstellen.

Es ist wichtig, sich daran zu erinnern, dass @staticmethod für Utility-Funktionen gedacht ist, die keinen Zustand der Klasse/Instanz benötigen, @classmethod für Fabrikmethoden, die auf die Klasse selbst zugreifen müssen und allgemeine Instanzmethoden zur Manipulation des Zustands des Objekts (self) verwendet werden.