Da Klassen Instanzen einer Metaklasse sind, ist es nicht unerwartet, dass sich eine "Instanzmethode" in der Metaklasse wie eine Klassenmethode verhält.
Ja, es gibt jedoch Unterschiede - und einige davon sind mehr als semantisch:
- Der wichtigste Unterschied besteht darin , dass ein Verfahren in der Metaklasse ist nicht „sichtbar“ aus einer Klasse Instanz . Dies liegt daran, dass die Attributsuche in Python (vereinfacht - Deskriptoren haben möglicherweise Vorrang) nach einem Attribut in der Instanz sucht. Wenn es in der Instanz nicht vorhanden ist, sucht Python in der Klasse dieser Instanz und die Suche wird fortgesetzt die Oberklassen der Klasse, aber nicht die Klassen der Klasse. Die Python-stdlib verwendet diese Funktion in der
abc.ABCMeta.register
Methode. Diese Funktion kann für immer verwendet werden, da Methoden, die sich auf die Klasse selbst beziehen, ohne Konflikte als Instanzattribute wiederverwendet werden können (eine Methode würde jedoch immer noch Konflikte verursachen).
- Ein weiterer Unterschied, obwohl offensichtlich ist, dass ein Verfahren erklärt , in der Metaklasse kann in mehreren Klassen zur Verfügung steht, soweit nicht anderweitig in Verbindung stehend - wenn Sie verschiedene Klassenhierarchien haben, nicht verwandt in was sie behandeln, wollen aber einige gemeinsame Funktionalität für alle Klassen Sie müssten eine Mixin-Klasse erstellen, die als Basis in beiden Hierarchien enthalten sein müsste (z. B. um alle Klassen in eine Anwendungsregistrierung aufzunehmen). (NB. Das Mixin ist manchmal ein besserer Anruf als eine Metaklasse.)
- Eine Klassenmethode ist ein spezialisiertes "Klassenmethoden" -Objekt, während eine Methode in der Metaklasse eine gewöhnliche Funktion ist.
Es kommt also vor, dass der Mechanismus, den Klassenmethoden verwenden, der " Deskriptorprotokoll " ist. Während normale Funktionen eine __get__
Methode enthalten, die das self
Argument einfügt, wenn sie aus einer Instanz abgerufen werden, und dieses Argument leer lässt, wenn sie aus einer Klasse abgerufen werden, hat ein classmethod
Objekt ein anderes __get__
, das die Klasse selbst (den "Eigentümer") als das einfügt erster Parameter in beiden Situationen.
Dies macht die meiste Zeit keine praktischen Unterschiede. Wenn Sie jedoch auf die Methode als Funktion zugreifen möchten, um ihr dynamisch einen Dekorator hinzuzufügen, oder eine andere, für eine Methode in der Metaklasse, wird meta.method
die Funktion abgerufen, die zur Verwendung bereit ist , während Sie es verwenden müssen cls.my_classmethod.__func__
, um es aus einer Klassenmethode abzurufen (und dann müssen Sie ein anderes classmethod
Objekt erstellen und es zurückweisen, wenn Sie etwas umbrechen).
Grundsätzlich sind dies die 2 Beispiele:
class M1(type):
def clsmethod1(cls):
pass
class CLS1(metaclass=M1):
pass
def runtime_wrap(cls, method_name, wrapper):
mcls = type(cls)
setattr(mcls, method_name, wrapper(getatttr(mcls, method_name)))
def wrapper(classmethod):
def new_method(cls):
print("wrapper called")
return classmethod(cls)
return new_method
runtime_wrap(cls1, "clsmethod1", wrapper)
class CLS2:
@classmethod
def classmethod2(cls):
pass
def runtime_wrap2(cls, method_name, wrapper):
setattr(cls, method_name, classmethod(
wrapper(getatttr(cls, method_name).__func__)
)
)
runtime_wrap2(cls1, "clsmethod1", wrapper)
Mit anderen Worten: Abgesehen von dem wichtigen Unterschied, dass eine in der Metaklasse definierte Methode von der Instanz aus sichtbar ist und ein classmethod
Objekt nicht, erscheinen die anderen Unterschiede zur Laufzeit dunkel und bedeutungslos - aber das geschieht, weil die Sprache nicht gehen muss mit speziellen Regeln für Klassenmethoden aus dem Weg: Beide Arten der Deklaration einer Klassenmethode sind als Folge des Sprachdesigns möglich - eine für die Tatsache, dass eine Klasse selbst ein Objekt ist, und eine andere als eine Möglichkeit unter vielen von die Verwendung des Deskriptorprotokolls, mit dem der Attributzugriff in einer Instanz und in einer Klasse spezialisiert werden kann:
Das classmethod
eingebaute System ist in nativem Code definiert, könnte jedoch nur in reinem Python codiert werden und würde genauso funktionieren. Der Balg der 5-Zeilen-Klasse kann als classmethod
Dekorateur verwendet werden, ohne dass Laufzeitunterschiede zum eingebauten @classmethod" at all (though distinguishable through introspection such as calls to
isinstance- , and even
Repräsentanten bestehen.
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
Über die Methoden hinaus ist es interessant zu bedenken, dass spezialisierte Attribute wie ein @property
auf der Metaklasse trotzdem als spezialisierte Klassenattribute funktionieren, ohne dass ein überraschendes Verhalten vorliegt.