Poiché le classi sono istanze di una metaclasse, non è inaspettato che un "metodo di istanza" sulla metaclasse si comporti come un metodo di classe.
Tuttavia, sì, ci sono differenze - e alcune sono più che semantiche:
- La differenza più importante è che un metodo nella metaclasse non è "visibile" da un'istanza di classe . Ciò accade perché la ricerca dell'attributo in Python (in modo semplificato - i descrittori possono avere la precedenza) cerca un attributo nell'istanza - se non è presente nell'istanza, Python cerca quindi nella classe di quell'istanza e quindi la ricerca continua le superclassi della classe, ma non sulle classi della classe. Lo stdlib di Python utilizza questa funzione nel
abc.ABCMeta.register
metodo. Tale funzionalità può essere utilizzata per sempre, poiché i metodi relativi alla classe stessa sono liberi di essere riutilizzati come attributi di istanza senza alcun conflitto (ma un metodo sarebbe comunque in conflitto).
- Un'altra differenza, sebbene ovvia, è che un metodo dichiarato nella metaclasse può essere disponibile in diverse classi, non altrimenti correlate - se si dispone di gerarchie di classi diverse, non correlate affatto in ciò che trattano, ma si desidera alcune funzionalità comuni per tutte le classi , dovresti inventare una classe mixin, che dovrebbe essere inclusa come base in entrambe le gerarchie (diciamo per includere tutte le classi in un registro dell'applicazione). (NB: il mixin a volte può essere una chiamata migliore di una metaclasse)
- Un classmethod è un oggetto "classmethod" specializzato, mentre un metodo nella metaclasse è una funzione ordinaria.
Quindi, accade che il meccanismo usato dai metodi di classe sia il " protocollo descrittore ". Mentre le normali funzioni presentano un __get__
metodo che inserirà l' self
argomento quando vengono recuperate da un'istanza e lascerà vuoto tale argomento quando recuperato da una classe, un classmethod
oggetto ha un diverso __get__
, che inserirà la classe stessa (il "proprietario") come primo parametro in entrambe le situazioni.
Ciò non fa differenze pratiche la maggior parte delle volte, ma se si desidera accedere al metodo come funzione, allo scopo di aggiungere dinamicamente l'aggiunta di decoratore ad esso, o qualsiasi altro, poiché un metodo nella metaclasse meta.method
recupera la funzione, pronto per essere utilizzato , mentre devi usarlo cls.my_classmethod.__func__
per recuperarlo da un metodo di classe (e quindi devi creare un altro classmethod
oggetto e assegnarlo di nuovo, se esegui un wrapping).
Fondamentalmente, questi sono i 2 esempi:
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)
In altre parole: a parte l'importante differenza che un metodo definito nella metaclasse è visibile dall'istanza e un classmethod
oggetto no, le altre differenze in fase di esecuzione sembreranno oscure e insignificanti, ma ciò accade perché il linguaggio non ha bisogno di andare fuori dalla sua strada con regole speciali per i metodi di classe: entrambi i modi di dichiarare un metodo di classe sono possibili, come conseguenza del design del linguaggio - uno, per il fatto che una classe è essa stessa un oggetto, e un'altra, come possibilità tra molti, di l'uso del protocollo descrittore che consente di specializzare l'accesso agli attributi in un'istanza e in una classe:
Il classmethod
builtin è definito in codice nativo, ma potrebbe essere codificato in puro pitone e funzionerebbe esattamente allo stesso modo. Il soffietto di classe a 5 righe può essere usato come classmethod
decoratore senza differenze di runtime rispetto al isinstance @classmethod" at all (though distinguishable through introspection such as calls to
repr` , and even
integrato ovviamente):
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
E, al di là dei metodi, è interessante tenere presente che attributi specializzati come a @property
sulla metaclasse funzioneranno come attributi di classe specializzati, lo stesso, senza alcun comportamento sorprendente.