Ponieważ klasy są instancjami metaklasy, nie jest niespodzianką, że „metoda instancji” na metaklasie będzie zachowywać się jak metoda klasy.
Jednak tak, istnieją różnice - a niektóre z nich są bardziej niż semantyczne:
- Najważniejszą różnicą jest to, że metoda w metaklasie nie jest „widoczna” z instancji klasy . Dzieje się tak, ponieważ wyszukiwanie atrybutów w Pythonie (w uproszczeniu - deskryptory mogą mieć pierwszeństwo) szuka atrybutu w instancji - jeśli nie ma go w instancji, Python następnie sprawdza klasę tej instancji, a następnie wyszukiwanie jest kontynuowane nadklasy klasy, ale nie w klasach klasy. Python stdlib korzysta z tej funkcji w
abc.ABCMeta.register
metodzie. Tę cechę można wykorzystać na dobre, ponieważ metody związane z samą klasą mogą być ponownie użyte jako atrybuty instancji bez żadnego konfliktu (ale metoda nadal powodowałaby konflikt).
- Inną różnicą, choć oczywistą, jest to, że metoda zadeklarowana w metaklasie może być dostępna w kilku klasach, niezwiązanych w inny sposób - jeśli masz różne hierarchie klas, w ogóle nie powiązane z tym , z czym mają do czynienia, ale chcesz mieć wspólną funkcjonalność dla wszystkich klas , musisz wymyślić klasę mixin, która musiałaby zostać uwzględniona jako podstawa w obu hierarchiach (na przykład w celu włączenia wszystkich klas do rejestru aplikacji). (Uwaga. Mixin może czasem być lepszym połączeniem niż metaklasa)
- Classmethod jest wyspecjalizowanym obiektem „classmethod”, podczas gdy metoda w metaklasie jest zwykłą funkcją.
Zdarza się więc, że mechanizmem stosowanym przez metody klas jest „ protokół deskryptora ”. Podczas gdy normalne funkcje zawierają __get__
metodę, która wstawi self
argument podczas pobierania go z instancji i pozostawi ten argument pusty podczas pobierania z klasy, classmethod
obiekt ma inny __get__
, który wstawi samą klasę („właściciela”) jako pierwszy parametr w obu sytuacjach.
Nie robi to praktycznie żadnych różnic, ale jeśli chcesz uzyskać dostęp do metody jako funkcji, w celu dynamicznego dodawania dekoratora do niej lub jakiejkolwiek innej, dla metody w metaklasie meta.method
pobiera funkcję, gotową do użycia , podczas gdy musisz użyć, cls.my_classmethod.__func__
aby pobrać go z metody klasy (a następnie musisz utworzyć inny classmethod
obiekt i przypisać go z powrotem, jeśli wykonasz jakieś zawijanie).
Zasadniczo są to 2 przykłady:
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)
Innymi słowy: oprócz istotnej różnicy, że metoda zdefiniowana w metaklasie jest widoczna z instancji, a classmethod
obiekt nie, inne różnice w czasie wykonywania będą wydawały się niejasne i pozbawione znaczenia - ale dzieje się tak, ponieważ język nie musi iść na uboczu dzięki specjalnym regułom dla metod klasowych: Oba sposoby deklarowania metody klasowej są możliwe, w konsekwencji projektu językowego - jeden, ponieważ klasa sama jest przedmiotem, a drugi, jako jedna z wielu możliwości zastosowanie protokołu deskryptora, który pozwala specjalizować się w dostępie do atrybutów w instancji i klasie:
classmethod
Wbudowane jest zdefiniowana w natywnego kodu, ale może to być po prostu zakodowane w czystym Pythonie i będzie działać w dokładnie taki sam sposób. The 5 linia klasa mieszek może być stosowany jako classmethod
dekorator bez różnic wykonawczych do wbudowanej w @classmethod" at all (though distinguishable through introspection such as calls to
isinstance , and even
repr` oczywiście):
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
Poza metodami warto pamiętać, że wyspecjalizowane atrybuty, takie jak @property
metaklasa, będą działać jako wyspecjalizowane atrybuty klasy, tak samo, bez żadnego zaskakującego zachowania.