Wanneer je met klassen in Python werkt, kom je de @classmethod decorator tegen. Dit heeft een duidelijk onderscheiden doel in vergelijking met @staticmethod of reguliere instantie methoden (Instance Method).

In dit artikel gaan we duidelijk bespreken wat @classmethod is, en in welke situaties we het moeten gebruiken in vergelijking met @staticmethod en instantie methoden.


1. Drie soorten methoden: Instance vs. Class vs. Static



Er zijn in Python klassen in wezen drie soorten methoden. Het grootste verschil is wat de eerste parameter is.

class MyClass:
    # 1. Instance Method (Instantiemethode)
    def instance_method(self, arg1):
        # 'self' ontvangt een instantie van de klasse.
        # Je kunt toegang krijgen tot en de instantie-attributen (self.x) modificeren.
        print(f"Instance method called with {self} and {arg1}")

    # 2. Class Method (Klassemethode)
    @classmethod
    def class_method(cls, arg1):
        # 'cls' ontvangt de klasse zelf (MyClass).
        # Het wordt vaak gebruikt om toegang te krijgen tot klasattribuut of om
        # een instantie te creëren als een 'fabriek'.
        print(f"Class method called with {cls} and {arg1}")

    # 3. Static Method (Statische methode)
    @staticmethod
    def static_method(arg1):
        # Ontvangt geen 'self' of 'cls'.
        # Het is een hulpfunctie die niet afhankelijk is van de status van de klasse of instantie.
        # Deze functie hoort alleen binnen de klasse thuis.
        print(f"Static method called with {arg1}")

# Voorbeeld van een methode-aanroep
obj = MyClass()

# Instantiemethode (opgeroepen via de instantie)
obj.instance_method("Hallo") 

# Klassemethode (kan worden opgeroepen via klasse of instantie)
MyClass.class_method("Wereld")
obj.class_method("Wereld") # Dit is ook mogelijk, maar het is duidelijker om het via de klasse te doen.

# Statische methode (kan worden opgeroepen via klasse of instantie)
MyClass.static_method("Python")
obj.static_method("Python")

Vergelijking in één oogopslag

Categorie Eerste Parameter Toegangsdoel Belangrijkste Gebruik
Instantiemethode self (instantie) Instantie-attribuut (self.name) Staat van het object manipuleren of benaderen
Klassemethode cls (klasse) Klasattribuut (cls.count) Alternatieve constructor (fabriek)
Statische methode geen geen Hulpfunctie die logisch gerelateerd is aan de klasse

2. De belangrijkste toepassing van @classmethod: Fabriek Methode

De belangrijkste en meest voorkomende gebruikssituatie van @classmethod is het definiëren van een "Alternatieve Constructor" of "Fabriek Methode".

De basisconstructor __init__ ontvangt meestal duidelijke parameters om een object te initialiseren. Maar soms is het nodig om een object op een andere manier te creëren (bijvoorbeeld door het parseren van een string, een woordenboek of bepaalde berekende waarde).

Door @classmethod te gebruiken, kun je een andere toegangspunt creëren die een klasse instantie retourneert naast __init__.

classmethod concept diagram

Voorbeeld: Maak een Person object vanuit een datumstring

Stel dat de Person klasse name en age ontvangt. Als we ook een object willen maken vanuit een string in het formaat 'naam-geboortejaar', wat moeten we doen?

import datetime

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

    def greet(self):
        print(f"Hallo, ik ben {self.name} en ik ben {self.age} jaar oud.")

    @classmethod
    def from_birth_year(cls, name, birth_year):
        """
        Ontvang het geboortejaar en bereken de leeftijd om een Person instantie te creëren.
        """
        current_year = datetime.datetime.now().year
        age = current_year - birth_year

        # 'cls' verwijst naar de Person klasse zelf.
        # Het is hetzelfde als return Person(name, age), maar,
        # het gebruik van 'cls' heeft voordelen bij inheritance (ondersteuning).
        return cls(name, age)

# Basisconstructor gebruiken
person1 = Person("Alice", 30)
person1.greet()

# Alternatieve constructor die met @classmethod is gemaakt gebruiken
person2 = Person.from_birth_year("MadHatter", 1995)
person2.greet()

Uitvoer:

Hallo, ik ben Alice en ik ben 30 jaar oud.
Hallo, ik ben MadHatter en ik ben 30 jaar oud. (op basis van huidige jaar 2025)

In het bovenstaande voorbeeld heeft de from_birth_year methode toegang tot de klasse zelf via de parameter cls. Vervolgens gebruikt het de berekende age om cls(name, age) aan te roepen, wat gelijk is aan het aanroepen van Person(name, age) en retourneert een nieuwe Person instantie.


3. @classmethod en inheritance



"Je kunt gewoon Person(...) aanroepen, waarom zou je cls(...) gebruiken?"

De ware kracht van @classmethod komt tot uiting bij inheritance. cls verwijst naar _deze specifieke klasse_ die de methode aanroept.

Laten we zeggen dat we een Student klasse maken die Person uitbreidt.

class Student(Person):
    def __init__(self, name, age, major):
        super().__init__(name, age) # Roept de __init__ van de ouder klasse aan
        self.major = major

    def greet(self):
        # Methode overdriting
        print(f"Hallo, ik ben {self.major} major {self.name} en ik ben {self.age} jaar oud.")

# Aanroepen van de @classmethod van de ouder in de Student klasse.
student1 = Student.from_birth_year("Jhon", 2005, "Informatica") 
# Oh, er treedt een TypeError op!

# Als je Student.from_birth_year aanroept, wordt 
# Person.from_birth_year uitgevoerd en dan, 
# op dat moment, is 'cls' 'Student'.
# Dus return cls(name, age) wordt return Student(name, age).
# Maar de __init__ van Student vereist een additioneel 'major' argument.

De bovenstaande code geeft een fout omdat de __init__ van Student major nodig heeft. Maar het belangrijkste punt is dat cls verwees naar Student in plaats van Person.

Als from_birth_year een @staticmethod was geweest, of als we Person(name, age) hardcoded hadden, zou een aanroep naar Student.from_birth_year een Person object in plaats van een Student object teruggegeven hebben.

@classmethod waarborgt door middel van cls dat zelfs als een kindklasse wordt aangeroepen, de correcte instantie van de kindklasse wordt gemaakt. (Natuurlijk moet je from_birth_year ook overschrijven als de ondertekening van __init__ van de kindklasse verandert.)


4. Samenvatting

Samenvattend over @classmethod, kunnen we het volgende zeggen:

  1. @classmethod ontvangt cls (de klasse zelf) als eerste parameter.

  2. De belangrijkste toepassing is 'alternatieve constructor (fabriek methode)'. Het wordt gebruikt om verschillende methoden voor objectcreatie te bieden buiten __init__.

  3. Het is krachtig als het samen met inheritance wordt gebruikt. Zelfs als het vanuit de kindklasse wordt aangeroepen, verwijst cls naar de correcte kindklasse, wat leidt tot het creëren van de instantie van die klasse.

Vergeet niet dat @staticmethod wordt gebruikt voor hulpfuncties die geen toegang nodig hebben tot de klasse/instantietoestand, @classmethod om toegang te krijgen tot de klasse zelf voor fabrieksmethodes, en reguliere instantiemethoden wanneer je met de toestand van een object (self) werkt. Dit helpt je om de code veel duidelijker en Pythonisch te schrijven.