Python: Was ist der Unterschied zwischen @staticmethod und @classmethod?
Der mechanische Unterschied ist eine Zeile: Eine classmethod bekommt die
Klasse, eine staticmethod bekommt nichts. Warum das wichtig ist, sieht man
erst, wenn jemand von deiner Klasse ableitet — und dort verbringt dieser Artikel
seine Zeit.
Was die beiden bekommen
class Demo:
def instanzmethode(self, x): ...
@classmethod
def klassenmethode(cls, x): ...
@staticmethod
def statische_methode(x): ...
instance_method got self=Demo, x=1
class_method got cls=Demo, x=1
static_method got nothing but x=1
classmethod und staticmethod lassen sich beide direkt auf der Klasse
aufrufen. Eine Instanzmethode nicht:
Demo.instance_method(2) -> TypeError: Demo.instance_method() missing 1 required positional argument: 'x'
Denn self wurde nie mitgegeben — Demo.instance_method ist bloß eine
Funktion, und du müsstest selbst eine Instanz übergeben.
Das ganze Argument in zwei Zeilen
Hier ist der Grund, warum die Wahl zählt. Zwei Fabrikmethoden an einer Basisklasse, von jeder Sorte eine:
class Basis:
@classmethod
def mit_cls(cls):
return cls()
@staticmethod
def mit_static():
return Basis()
class Kind(Basis):
pass
Child.make_cls() -> Child
Child.make_static() -> Base <- hardcoded Base
cls ist die Klasse, über die aufgerufen wurde, die classmethod baut also
ein Kind. Die staticmethod nennt Basis in ihrem Rumpf und liefert deshalb
immer eine Basis zurück, ganz gleich wer sie aufruft.
Das ist der gesamte Grund für classmethod, und deshalb müssen alternative
Konstruktoren classmethods sein:
class Punkt:
@classmethod
def aus_string(cls, text):
x, y = (int(t) for t in text.split(","))
return cls(x, y)
Point.from_string('1,2') : Point(1, 2)
Point3D.from_string('3,4') : Point3D(3, 4) <- correct subclass
Schreib return Punkt(x, y) statt return cls(x, y) und jede abgeleitete
Klasse bekommt stillschweigend den falschen Typ zurück. Es ist derselbe Fehler
wie new self() gegenüber new static() in PHP, falls dir der schon einmal
begegnet ist.
cls übernimmt auch überschriebene Attribute
Nicht nur den Typ, sondern alles was die abgeleitete Klasse geändert hat:
class Tier:
laut = "..."
@classmethod
def beschreibe(cls):
return f"{cls.__name__} macht {cls.laut}"
Dog.describe() : Dog says woof
Dog.describe_static() : Animal says ... <- still the base class
Eine classmethod ist also immer dann die richtige Wahl, wenn sich das
Verhalten nach Einstellungen auf Klassenebene richten soll, die abgeleitete
Klassen überschreiben dürfen.
Wann eine staticmethod richtig ist
Wenn die Funktion tatsächlich weder das eine noch das andere braucht. Sie ist ein Helfer, der der Lesbarkeit und der Einordnung wegen zur Klasse gehört, und sonst nichts:
class Temperatur:
@staticmethod
def c_nach_f(celsius):
return celsius * 9 / 5 + 32
Kein self, kein cls, keine Klassenattribute. Wenn du eines davon willst,
wolltest du eine classmethod.
Die ehrliche Alternative ist eine Funktion auf Modulebene. Eine staticmethod
bringt dir die Gruppierung und die Möglichkeit, sie in einer Ableitung zu
überschreiben; ist dir beides egal, ist die einfache Funktion einfacher.
Was diese Dinge tatsächlich sind
type(Demo.static_method) : function
type(Demo.class_method) : method
type(Demo.instance_method) : function
type(d.instance_method) : method <- bound
Eine staticmethod ist eine ganz gewöhnliche Funktion, die zufällig im
Namensraum einer Klasse liegt — genau das steht oben. Eine classmethod ist
bereits an die Klasse gebunden und zeigt sich deshalb auch über die Klasse als
method. Und eine Instanzmethode ist an der Klasse eine function, an einer
Instanz aber eine gebundene method — das ist das Deskriptor-Protokoll bei der
Arbeit.
Seit Python 3.10 sind staticmethod-Objekte direkt aufrufbar, du kannst eine
also schon im Klassenrumpf während der Definition verwenden. Das war vorher
eine kleine Ärgerlichkeit.
Der Fehler, der überlebt
Vergiss den Dekorator und der Fehlschlag kommt verzögert:
class Ups:
def helfer(x):
return x * 2
Oops.helper(5) : 10 <- works when called on the CLASS
Oops().helper(5) -> TypeError: Oops.helper() takes 1 positional argument but 2 were given
Auf der Klasse aufgerufen funktioniert es, weil nichts gebunden wird und die
5 in x landet. Auf einer Instanz aufgerufen wird die Instanz als erstes
Argument übergeben, x wird zum Objekt, und dein eigentliches Argument hat
keinen Platz mehr.
Die Fehlermeldung verwirrt beim ersten Mal — du hast ein Argument übergeben und
sie sagt, es wären zwei. Das unsichtbare ist self.
Ein praktischer Hinweis
@staticmethod und @classmethod gehören ganz nach außen, also über alle
anderen Dekoratoren:
class Geordnet:
@staticmethod
def richtig():
return "ok"
Sie verpacken die Funktion in einen Deskriptor. Alles, was darüber steht, würde also den Deskriptor dekorieren und nicht die Funktion — und das tut fast nie das Gewünschte.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).