Які відмінності між методом classsethod і методом метакласу?


12

У Python я можу створити метод класу за допомогою @classmethodдекоратора:

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

Крім того, я можу використовувати звичайний метод (екземпляр) для метакласу:

>>> 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'>

Як показано результатами C.f(), ці два підходи забезпечують аналогічну функціональність.

Які відмінності між використанням @classmethodта використанням звичайного методу в метакласі?

Відповіді:


5

Оскільки класи - це випадки метакласу, несподівано, що "метод екземпляра" в метакласі буде вести себе як класний метод.

Однак так, є відмінності - і деякі з них є більш ніж семантичними:

  1. Найважливіша відмінність полягає в тому, що метод у метакласі не "видимий" з екземпляра класу . Це відбувається тому, що пошук атрибутів у Python (спрощеним способом - дескриптори можуть мати перевагу) пошук атрибута в екземплярі - якщо його немає в екземплярі, то Python шукає у класі цього примірника, а потім пошук продовжується далі суперкласи класу, але не по класах класу. Python stdlib використовує цю функцію в abc.ABCMeta.registerметоді. Ця функція може бути використана на користь, оскільки методи, пов'язані з самим класом, можуть бути повторно використані як атрибути екземпляра без будь-якого конфлікту (але метод все одно конфліктує).
  2. Ще одна відмінність, хоча очевидна, полягає в тому, що метод, оголошений у метакласі, може бути доступний у кількох класах, не пов'язаних інакше - якщо у вас різні ієрархії класів, взагалі не пов'язані в чим вони мають справу, але хочуть деякої загальної функціональності для всіх класів , вам доведеться придумати клас змішання, який повинен бути включений в базу в обох ієрархіях (скажімо, для включення всіх класів у реєстр програм). (Зверніть увагу: міксин іноді може бути кращим, ніж метаклас)
  3. Класовий метод - це спеціалізований об'єкт "classmethod", тоді як метод у метакласі - це звичайна функція.

Отже, буває, що механізм, який використовують методи classmethod, - це " протокол дескриптора ". У той час як звичайні функції мають __get__метод, який буде вставляти selfаргумент під час отримання з екземпляра, а цей аргумент залишатиметься порожнім при отриманні з класу, classmethod об'єкт має інший __get__, який буде вставляти сам клас ("власник") як Перший параметр в обох ситуаціях.

Це не робить практичних відмінностей більшу частину часу, але якщо ви хочете отримати доступ до методу як функції, для цілей додавання до нього динамічного додавання декоратора чи будь-якого іншого, для методу в метакласі meta.methodотримує функцію, готову до використання , тоді як вам потрібно скористатися cls.my_classmethod.__func__ для отримання його з класного методу (і тоді вам доведеться створити інший classmethodоб'єкт і призначити його назад, якщо ви робите певну обмотку).

В основному, це два приклади:


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)

Іншими словами: крім важливої ​​різниці, що метод, визначений у метакласі, видно з екземпляра та aclassmethod об'єкт - ні, інші відмінності під час виконання будуть здаватися незрозумілими та безглуздими - але це відбувається тому, що мови не потрібно йти не виходить із спеціальних правил для класних методів: Обидва способи оголошення класового методу можливі, як наслідок мовної конструкції - один, для того, що клас сам є об’єктом, а інший, як можливість серед багатьох, використання протоколу дескриптора, який дозволяє спеціалізувати доступ до атрибутів в екземплярі та класі:

classmethodВбудований визначаються в машинному коді, але він може просто бути закодований в чистому пітона й буде працювати в точно так же. Знизу 5-го рядка можна використовувати як classmethodдекоратор без різниць у виконанні, звичайно, вбудована @classmethod" at all (though distinguishable through introspection such as calls toістота- , and evenповтор).


class myclassmethod:
    def __init__(self, func):
        self.__func__ = func
    def __get__(self, instance, owner):
        return lambda *args, **kw: self.__func__(owner, *args, **kw)

І, крім методів, цікаво пам’ятати, що спеціалізовані атрибути, такі як @property«метаклас», працюватимуть як атрибути спеціалізованого класу, точно такі ж, зовсім не дивно.


2

Коли ви @classmethodформулюєте це так, як ви робили у запитанні, метакласи можуть виглядати схоже, але вони мають досить різні цілі. Клас, який вводиться в @classmethodаргумент 's, зазвичай використовується для побудови екземпляра (тобто альтернативного конструктора). З іншого боку, метакласи зазвичай використовуються для модифікації самого класу (наприклад, як те, що робить Django зі своїми моделями DSL).

Це не означає, що ви не можете змінювати клас всередині методу class. Але тоді виникає питання, чому ви в першу чергу не визначили клас так, як ви хочете його змінити? Якщо ні, то може запропонувати рефактор використовувати декілька класів.

Розкриємо перший приклад трохи.

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

Запозичившись із документів Python , вищесказане розшириться на щось подібне:

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)

Зверніть увагу, як __get__можна взяти екземпляр або клас (або обидва), і, таким чином, ви можете зробити і те, C.fі інше C().f. Це в відміну від прикладу метаклассом ви даєте , які будуть кидатися AttributeErrorна C().f.

Більше того, у прикладі метакласу fне існує в C.__dict__. Переглядаючи атрибут за fдопомогою C.f, інтерпретатор розглядає, C.__dict__а потім, не зумівши знайти, дивиться на type(C).__dict__(що це таке M.__dict__). Це може мати значення , якщо ви хочете гнучкість , щоб перевизначити fв C, хоча я сумніваюся , що це буде коли - або мати практичне застосування.


0

У вашому прикладі різниця полягатиме в деяких інших класах, які матимуть M як свій метаклас.

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'

Ось докладніше про метакласи Які є деякі (конкретні) випадки використання для метакласів?


0

І @classmethod, і Metaclass різні.

Все в пітоні - це предмет. Кожна річ означає кожну річ.

Що таке метаклас?

Як говорилося, кожна річ є предметом. Класи також є об'єктами, адже класи - це екземпляри інших загадкових об'єктів, які формально називаються метакласами. Метаклас за замовчуванням у python є "type", якщо не вказано

За замовчуванням усі визначені класи - це екземпляри типу.

Класи - це екземпляри мета-класів

Небагато важливих моментів - це зрозуміти доцільну поведінку

  • Оскільки класи - це екземпляри мета-класів.
  • Як і кожен створений об'єкт, як об'єкти (екземпляри) отримують свої атрибути від класу. Клас отримає свої атрибути від Meta-Class

Розглянемо наступний кодекс

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()

Де,

  • клас C - це примірник класу Meta
  • "class C" - об'єкт класу, який є примірником "class Meta"
  • Як і будь-який інший об'єкт (екземпляр), "клас C" має доступ до його атрибутів / методів, визначених у його класі "клас Meta"
  • Отже, розшифровка "C.foo ()". "C" - це примірник "Meta", а "foo" - це метод виклику через екземпляр "Meta", який є "C".
  • Перший аргумент методу "foo" - це посилання на екземпляр, а не клас на відміну від "classmethod"

Ми можемо перевірити, ніби "клас C" є примірником "Class Meta

  isinstance(C, Meta)

Що таке класний метод?

Кажуть, що методи Python пов'язані. Оскільки python накладає обмеження, до цього методу слід застосовувати лише екземпляр. Іноді ми можемо захотіти викликати методи безпосередньо через клас без жодного екземпляра (як статичні члени в java), не маючи необхідності створювати будь-який екземпляр. Для виклику методу потрібен екземпляр за замовчуванням. Як обхідний спосіб python надає вбудований функціональний класметод для прив'язки даного методу до класу замість екземпляра.

Оскільки методи класу пов'язані з класом. Він має хоча б один аргумент, який посилається на сам клас, а не на екземпляр (self)

якщо використовується вбудований метод / клас декоратора, метод Перший аргумент буде посиланням на клас, а не на екземпляр

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

Оскільки ми використовували "classmethod", ми називаємо метод "foo", не створюючи жодного примірника наступним чином

ClassMethodDemo.foo()

Вищеназваний метод поверне значення True. Оскільки перший аргумент cls справді посилається на "ClassMethodDemo"

Підсумок:

  • Отримайте перший аргумент Classmethod, який є "посиланням на клас (традиційно його називають cls)"
  • Методи метакласів не є класними методами. Методи мета-класів отримують перший аргумент, який є "посиланням на примірник (традиційно його називають self), а не клас"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.