Jakie są różnice między metodą klasową a metaklasą?


12

W Pythonie mogę utworzyć metodę klasy za pomocą @classmethoddekoratora:

>>> class C:
...     @classmethod
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>

Alternatywnie, mogę użyć normalnej (instancji) metody na metaklasę:

>>> class M(type):
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> class C(metaclass=M):
...     pass
...
>>> C.f()
f called with cls=<class '__main__.C'>

Jak wynika z danych wyjściowych C.f(), te dwa podejścia zapewniają podobną funkcjonalność.

Jakie są różnice między używaniem @classmethodi używaniem normalnej metody na metaklasie?

Odpowiedzi:


5

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:

  1. 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.registermetodzie. 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).
  2. 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)
  3. 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 selfargument podczas pobierania go z instancji i pozostawi ten argument pusty podczas pobierania z klasy, classmethodobiekt 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.methodpobiera 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 classmethodobiekt 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 classmethodobiekt 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:

classmethodWbudowane 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 classmethoddekorator bez różnic wykonawczych do wbudowanej w @classmethod" at all (though distinguishable through introspection such as calls toisinstance , and evenrepr` 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 @propertymetaklasa, będą działać jako wyspecjalizowane atrybuty klasy, tak samo, bez żadnego zaskakującego zachowania.


2

Kiedy wyrazisz to tak, jak w pytaniu, @classmethodmetaklasy i mogą wyglądać podobnie, ale mają raczej różne cele. Klasa wprowadzona do @classmethodargumentu jest zwykle używana do konstruowania instancji (tj. Alternatywnego konstruktora). Z drugiej strony, metaklasy są zwykle używane do modyfikowania samej klasy (np. Jak to robi Django z modelami DSL).

Nie oznacza to, że nie można modyfikować klasy wewnątrz metody klasy. Ale wtedy pojawia się pytanie, dlaczego nie zdefiniowałeś klasy w sposób, w jaki chcesz ją zmodyfikować? Jeśli nie, może to sugerować refaktorowi użycie wielu klas.

Rozwińmy trochę pierwszy przykład.

class C:
    @classmethod
    def f(cls):
        print(f'f called with cls={cls}')

Pożyczając z dokumentów Python , powyższe rozwinie się do czegoś takiego:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

class C:
    def f(cls):
        print(f'f called with cls={cls}')
    f = ClassMethod(f)

Zauważ, jak __get__można wziąć instancję lub klasę (lub obie), a zatem możesz wykonać obie C.fi C().f. Jest to odmienne od podanego przykładu z metaklasy, który rzuci AttributeErrorzaC().f .

Ponadto w przykładzie z metaklasy fnie istnieje C.__dict__. Podczas wyszukiwania atrybutu fza C.fpomocą interpretera patrzy się, C.__dict__a po nieudanym znalezieniu patrzy na type(C).__dict__(co jest M.__dict__). Może to znaczenia, jeśli chcesz elastyczność, aby zastąpić fw C, chociaż wątpię, to nigdy nie będzie praktycznego wykorzystania.


0

W twoim przykładzie różnica byłaby w niektórych innych klasach, dla których metaklasa M jest ustawiona.

class M(type):
    def f(cls):
        pass

class C(metaclass=M):
    pass

class C2(metaclass=M):
    pass

C.f()
C2.f()
class M(type):
     pass

class C(metaclass=M):
     @classmethod
     def f(cls):
        pass

class C2(metaclass=M):
    pass

C.f()
# C2 does not have 'f'

Oto więcej o metaklasach Jakie są (konkretne) przypadki użycia metaklas?


0

Zarówno @classmethod, jak i Metaclass są różne.

Wszystko w Pythonie jest przedmiotem. Każda rzecz oznacza każdą rzecz.

Co to jest Metaklasa?

Jak już powiedziano, każda rzecz jest przedmiotem. Klasy są również obiektami, w rzeczywistości klasy są instancjami innych tajemniczych obiektów formalnie zwanych meta-klasami. Domyślna metaklasa w pythonie to „typ”, jeśli nie jest określona

Domyślnie wszystkie zdefiniowane klasy są instancjami typu.

Klasy są instancjami Meta-klas

Kilka ważnych punktów to zrozumienie zachowań metiary

  • Ponieważ klasy są instancjami meta klas.
  • Jak każdy utworzony obiekt, obiekty (instancje) otrzymują swoje atrybuty z klasy. Klasa otrzyma swoje atrybuty z Meta-Class

Rozważ następujący kod

class Meta(type):
    def foo(self):
        print(f'foo is called self={self}')
        print('{} is instance of {}: {}'.format(self, Meta, isinstance(self, Meta)))

class C(metaclass=Meta):
    pass

C.foo()

Gdzie,

  • klasa C jest instancją klasy Meta
  • „klasa C” to obiekt klasy będący instancją „klasy Meta”
  • Jak każdy inny obiekt (instancja) „klasa C” ma dostęp, jego atrybuty / metody zdefiniowane w jej klasie „class Meta”
  • Zatem dekodowanie „C.foo ()”. „C” to instancja „Meta”, a „foo” to metoda wywołująca instancję „Meta”, czyli „C”.
  • Pierwszy argument metody „foo” odnosi się do instancji, a nie klasy, w przeciwieństwie do „classmethod”

Możemy zweryfikować, jakby „klasa C” była instancją „Class Meta

  isinstance(C, Meta)

Co to jest metoda klasowa?

Mówi się, że metody Pythona są powiązane. Ponieważ Python nakłada ograniczenia, metoda musi być wywoływana tylko z instancją. Czasami możemy chcieć wywoływać metody bezpośrednio przez klasę bez żadnej instancji (podobnie jak statyczne elementy w java) bez konieczności tworzenia dowolnej instancji. Do wywołania metody wymagana jest instancja domyślna. Jako obejście to python zapewnia wbudowaną funkcję classmethod do powiązania danej metody z klasą zamiast z instancją.

Ponieważ metody klas są powiązane z klasą. Wymaga co najmniej jednego argumentu, który odnosi się do samej klasy zamiast instancji (self)

jeśli używana jest metoda wbudowanej funkcji / klasy dekoratora. Pierwszym argumentem będzie odwołanie do klasy zamiast wystąpienia

class ClassMethodDemo:
    @classmethod
    def foo(cls):
        print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')

Ponieważ użyliśmy metody „classmethod”, wywołujemy metodę „foo” bez tworzenia żadnej instancji w następujący sposób

ClassMethodDemo.foo()

Wywołanie metody powyżej spowoduje zwrócenie wartości True. Ponieważ pierwszy argument cls jest rzeczywiście odniesieniem do „ClassMethodDemo”

Podsumowanie:

  • Classmethod otrzymuje pierwszy argument, który jest „odwołaniem do samej klasy (tradycyjnie zwanej cls)”
  • Metody meta klas nie są metodami klasowymi. Metody Meta-klas otrzymują pierwszy argument, który jest „odwołaniem do instancji (tradycyjnie nazywanej ja), a nie do klasy”
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.