Instanzmethoden, statische Methoden und Klassenmethoden in Python

Es dauert nicht lange, da wird man sich als Python-Beginner die Frage stellen, was es mit Instanz-, Klassen- bzw. statischen Methoden auf sich hat und was diese voneinander unterscheidet. Wenn Sie sich gerade diese Fragen stellen, dann haben Sie möglicherweise Quellcode vor sich, der den Dekorator (Dekoratoren beginnen mit dem @-Zeichen) @classmethod bzw. @staticmethod enthält. Oder Sie suchen gerade nach Möglichkeiten, wie sie Ihren Quellcode besser, d.h. logischer, aufbauen können. Dazu sind die @classmethod und @staticmethod im Grunde da: Sie helfen dabei, objektorientierten Pythoncode logisch aufzubauen. Erfahren Sie in diesem Beitrag, was die Methodentypen unterscheidet und wie sie eingesetzt werden.

Die Beispielklasse ‚Human‘

Lassen Sie uns zunächst eine Beispielklasse Human aufbauen, anhand derer wir das Thema bearbeiten. Mit Human erzeugen wir uns Instanzen der Klasse Human – Personen könnte man auch sagen. Ein Human hat in unserem Fall 3 Attribute:

  • Einen Namen
  • Ein Alter
  • Ein Geschlecht

Außerdem verfügt die Klasse über 3 Methoden:

  • introduce (Instanzmethode)
  • definition (Klassenmethode)
  • add (Statische Methode)

Mit der Instanzmethode introduce kann der Human sich selber vorstellen. Durch den Aufruf der Klassenmethode definition wird eine Zeile Wikipedia-Text ausgegeben (Zugegeben, dass ist eine sehr unvollständige Definition von Menschsein, aber lassen wir an dieser Stelle keine Strenge walten). Die @staticmethod add macht nichts anderes als 2 Zahlen zu addieren.

In [1]:
class Human():
    """A class for generating human beings"""
    
    def __init__(self, name:str, age:int, sex:str):
        self.name = name
        self.age = age
        self.sex = sex
    
    def introduce(self):
        print('Hi! My name is ' + self.name)
       
    @classmethod
    def definition(cls):
        print('Der Mensch, auch Homo sapiens, ist ein höheres Säugetier.')
        
    @staticmethod
    def add(number1:int, number2:int):
        return number1 + number2

Lassen Sie uns nun überlegen, was die Instanzmethode, die Klassenmethode und die statische Methode auszeichnet und warum sie in dieser Implementierung einer sinnigen Logik folgen.

Die Instanzmethode ‚introduce‘

Einer Instanzmethode wie introduce wird als erster Parameter self übergeben. Über den Parameter self hat die Methode Zugriff auf alle Eigenschaften derjenigen Instanz, aus der heraus die Methode später aufgerufen wird.

Das folgende Beispiel zeigt den Aufruf von introduce. Die Eigenschaften von Peter, einer Instanz der Klasse Human, sind der Methode introduce bei ihrem Aufruf bekannt. Aus der Instanz wird innerhalb der print Anweisung auf self.name zugegriffen und die entsprechende Grußformel ausgeprintet. Genausogut könnte man bei der Definition der Methode auch auf die anderen Attribute einer Instanz human zugreifen, also age und sex.

In [2]:
# Wir initialisieren eine Instanz der Klasse human
peter = Human(name='Peter',
              age=30,
              sex='M')

# Lassen Sie sich nicht irritieren. Die erstellte Instanz trägt die Bezeichnung Peter, gleichzeitig ist Peter 
# auch der Wert des Attributs name. Beides sind verschiedene Dinge.
peter.introduce()
Hi! My name is Peter

Die Klassenmethode ‚definition‘

Kommen wird nun zur @classmethod definition. Grundsätzlich lässt sich diese auch wie eine Instanzmethode aufrufen.

In [3]:
peter.definition()
Der Mensch, auch Homo sapiens, ist ein höheres Säugetier.

Jetzt aber das Erstaunliche, sozusagen die Quintessenz der @classmethod. Die Methode lässt sich ebenfalls auch über die Klasse Human aufrufen.

In [4]:
Human.definition()
Der Mensch, auch Homo sapiens, ist ein höheres Säugetier.

Sobald die Klasse definiert ist können wir bereits die definition-Methode aufrufen. Unsere Klasse human betreffend können wir inhaltlich sagen: Sie weiß, wie jede Instanz eines Menschen definiert ist. Genauso weiß jede auch Instanz von Human, wie sie definiert ist.
Der Aufruf der Instanzmethode human.introduce() führt hingegen zu einem error mit der Meldung: missing 1 required positional argument: ’self‘. ’self‘ fehlt diese Instanzmethode, weil sie nur auf einer Instanz von human auszuführen ist. Zur Erinnerung die Definition von introduce:

    def introduce(self):
        print('Hi! My name is ' + self.name)

Lassen Sie uns anschauen, was für die einzelnen Methoden ausgegeben wird, wenn wir sie ausprinten:

In [5]:
print(Human.definition)
print(Human.introduce)
print(peter.definition)
print(peter.introduce)
<bound method Human.definition of <class '__main__.Human'>>
<function Human.introduce at 0x0000019D87056B70>
<bound method Human.definition of <class '__main__.Human'>>
<bound method Human.introduce of <__main__.Human object at 0x0000019D866B0A20>>

Wir sehen an der Ausgabe von Human.introduce, dass die Methode nicht an die Klasse Human gebunden ist, sondern an eine Instanz von Human. Alle anderen Ausgaben von print geben an, dass die Methode an das zugehörige Objekt gebunden ist – also mit dem genannten Objekt innerhalb von print ausgeführt werden kann.

Führen wir uns nochmals die @classmethod vor Augen:

@classmethod
    def definition(cls):
        print('Der Mensch, auch Homo sapiens, ist ein höheres Säugetier.')

Dieser Methode wird nicht der Parameter self übergeben, sondern cls – also die Klasse selbst. Innehalb von definition könnten wir nun alle Informationen die zur Klasse Human gehören verwenden. Zum Beispiel könnten wir uns statt der Wikipedia Definition den Docstring der Klasse ausgeben:

@classmethod
    def definition(cls):
        print(cls.__doc__)

Die statische Methode ‚add‘

Statische Methoden zeichnen sich dadurch aus, dass ihnen kein Parameter übergeben, der irgendeinen Bezug zur Klasse oder Methode hat – einer statischen Methode wird also weder cls noch self übergeben. So natürlich auch in der Beispielmethode add. Diese verlangt als Eingangsparameter 2 Integerwerte – egal woher diese kommen.

Lassen Sie uns die Methode einmal ausführen und uns vorher eine Preisfrage formulieren: Lässt sich die Methode nun über die Instanz oder die Klasse ausführen? Kriegen wir einen error, wenn wir sagen

peter.add(1,2)

oder

human.add(1,2)

? Die Antwort lautet: Wir kriegen in beiden Fällen keinen Fehler und es wird der korrekte Wert 3 ausgegeben. Zugegeben, ganz intuitiv ist das Verhalten nicht, aber lassen Sie uns festhalten: Die @staticmethod

  1. erhält als Eingangsparameter weder Klasse noch Methode
  2. sie lässt sich sowohl über die Klasse als auch eine Instanz aufrufen.

Die Unterscheidung von Klassenmethode zur statischen Methode sorgt ausschließlich der jeweilige Dekorator. Lassen Sie uns noch einmal anhand eines Mini-Beispiels ausprobieren, was diese im Detail bewirken. Beide Methoden des Beispiels returnieren lediglich die Parameter, die ihnen übergeben werden.

In [6]:
class Example():
    """Just a little example"""
    
    @staticmethod
    def static_method(*args):
        return args
    
    @classmethod
    def class_method(*args):
        return args
In [7]:
# Wir instanziieren obj - eine Instanz von Example
obj = Example()

print('Aufruf von static_method:',obj.static_method())
print('Aufruf von class_method:',obj.class_method())
Aufruf von static_method: ()
Aufruf von class_method: (<class '__main__.Example'>,)

Der Aufruf der @staticmethod erfolgt erwartungsgemäß unspektakulär. Wir übergeben keine Parameter an die Methode und bekommen dementsprechend nichts returniert. Wie wir allerdings sehen, hat der Dekorator @classmethod dafür gesorgt, dass der Parameter *args nun die Eigenschaften der Klasse sind, die an die Methode übergeben werden.

Im obigen Beispiel der Klasse human hatten wir nicht *args, sondern cls als ersten Parameter übergeben und für den gleichen Effekt gesort, den wir auch in dem kleinem Beispiel der Klasse Example sehen: Die Klasse wird der Methode übergeben. Was wir hier lernen ist, dass der Name cls kein festgelegtes Keyword ist, mit der in Python und seinen @classmethods gearbeitet wird, sondern lediglich ein konventioneller Bezeichner ist. Sie machen sich in der Python Community Freunde, wenn Sie cls nutzen! Das gleiche gilt auch für self. Sie müssen Instanzmethoden nicht mit der Parameterbezeichnung self definieren, aber wenn Ihnen die Lesbarkeit Ihres Codes wichtig ist, sollten Sie das tun!

Sie mögen sich nun aber vielleicht (zurecht) fragen, was eigentlich der entscheidende Vorteil einer @staticmethod ist. Ganz richtig vermuten Sie bereits, dass unsere Methode add aus der Klasse Human auch als Funktion definiert sein könnte.

def add(number1:int, number2:int):
        return number1 + number2

Die Ausführung wäre dann nicht mehr an die Klasse gekoppelt und Sie könnten schreiben:

add(1,2)
>>> 3

Korrekt! Die Funktion wäre nicht mehr an die Klasse gekoppelt und das ist der springende Punkt. Sie (ich bin so frei und mache diese Unterstellung zu rhetorischen Zwecken) wollen aber, dass genau das passiert! In unserer Klasse Human bspw. machen wir mit der Methode add deutlich, dass ein Mensch die Addition vornimmt und nicht irgendetwas anderes. Wir sehen also, dass die @staticmethod vor allem dazu dient, unseren Code logisch zu gestalten.

Zurück zum Start: Warum macht es nun für die Klasse Human Sinn, die Methoden eben auf diese Weise zu implementieren?
Der Fall introduce ist recht einfach: Erst wenn eine Instanz existiert, kann diese sich vorstellen. D.h. die Methode sieht vor, dass eine Person sich vorstellt und nicht die Klasse selbst.
Die @classmethod intendiert, dem Anwender informationen darüber zukommen zu lassen, was einen Menschen definiert. Die Definition ist logisch der Klasse zugeordnet und daher ist sie zurecht als Klassenmethode eingebunden. Und wie gesagt: Die @staticmethod add zeigt, dass nur der Mensch 2 Zahlen addieren kann und diese Fähigkeit nicht sonst irgendwo aus dem Äther stammt.

Die @classmethod in der Anwendung

Ein häufig anzutreffendes Beispiel für die Anwendung der @classmethod ist die Auszählung der Instanzen einer Klasse. In dem Fall der Klasse Human können wir dementsprechend mit einer @classmethod eine Information darüber erhalten, wie viele Personen (Instanzen) erzeugt wurden.

Um dieses Verhalten umzusetzen wird zunächst die Klassenvariable n_humans mit dem Wert 0 bei der Klassendefinition initialisiert. In der init-Methode wird n_humans inkrementiert – d.h. immer wenn eine Instanz von Human erstellt wird, erhöht sich der Wert von n_humans um 1. Mit der Klassenmethode counter wird die Variable n_humans abgefragt.

In [8]:
class Human():
    
    n_humans = 0

    def __init__(self, name:str, age:int, sex:str):
        self.name = name
        self.age = age
        self.sex = sex
        
        Human.n_humans += 1
        
    @classmethod
    def counter(cls):
        return cls.n_humans
        
In [9]:
Peter = Human(name = 'Peter',
              age = 35, 
              sex = 'M')

Lisa = Human(name = 'Lisa',
             age = 28, 
             sex = 'W')

Human.n_humans
Lisa.n_humans
Out[9]:
2