Оскільки класи - це випадки метакласу, несподівано, що "метод екземпляра" в метакласі буде вести себе як класний метод.
Однак так, є відмінності - і деякі з них є більш ніж семантичними:
- Найважливіша відмінність полягає в тому, що метод у метакласі не "видимий" з екземпляра класу . Це відбувається тому, що пошук атрибутів у Python (спрощеним способом - дескриптори можуть мати перевагу) пошук атрибута в екземплярі - якщо його немає в екземплярі, то Python шукає у класі цього примірника, а потім пошук продовжується далі суперкласи класу, але не по класах класу. Python stdlib використовує цю функцію в
abc.ABCMeta.register
методі. Ця функція може бути використана на користь, оскільки методи, пов'язані з самим класом, можуть бути повторно використані як атрибути екземпляра без будь-якого конфлікту (але метод все одно конфліктує).
- Ще одна відмінність, хоча очевидна, полягає в тому, що метод, оголошений у метакласі, може бути доступний у кількох класах, не пов'язаних інакше - якщо у вас різні ієрархії класів, взагалі не пов'язані в чим вони мають справу, але хочуть деякої загальної функціональності для всіх класів , вам доведеться придумати клас змішання, який повинен бути включений в базу в обох ієрархіях (скажімо, для включення всіх класів у реєстр програм). (Зверніть увагу: міксин іноді може бути кращим, ніж метаклас)
- Класовий метод - це спеціалізований об'єкт "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
«метаклас», працюватимуть як атрибути спеціалізованого класу, точно такі ж, зовсім не дивно.