Метаклассы в Python

от автора

Перевод статьи подготовлен в преддверии старта курса «Python Developer. Basic».


Метаклассы – это такие классы, экземпляры которых сами являются классами. Подобно тому, как «обычный» класс определяет поведение экземпляров класса, метакласс определяет и поведение классов, и поведение их экземпляров.

Метаклассы поддерживаются не всеми объектно-ориентированными языками программирования. Те языки программирования, которые их поддерживают, значительно отличаются по способу их реализации. Но в Python метаклассы есть.

Некоторые программисты рассматривают метаклассы в Python как «решения, которые ждут или ищут задачу».

У метаклассов множество применений. Выделим несколько из них:

  • Логирование и профилирование;

  • Тестирование интерфейса;

  • Регистрация классов во время создания;

  • Автоматическое создание свойств;

  • Прокси;

  • Автоматическая блокировка/синхронизация ресурсов.

Определение метаклассов

В целом, метаклассы определяются также, как и любые другие классы в Python, но это классы, которые наследуются от «типа». Еще одно отличие в том, что метакласс вызывается автоматически, когда оператор класса, использующий метакласс, заканчивается. Другими словами, если ключевое слово metaclass не передается после базовых классов заголовка класса (однако базовых классов может и не быть), то будет вызван type() (т.е. _call_ типа). С другой стороны, если ключевое слово metaclass используется, то назначенный ему класс будет вызываться вместо type.

Давайте создадим совсем простой метакласс. Он ничего не умеет, кроме вывода содержимого своих аргументов в методе_new_ и возврата результата вызова type._new_:

class LittleMeta(type):     def __new__(cls, clsname, superclasses, attributedict):         print("clsname: ", clsname)         print("superclasses: ", superclasses)         print("attributedict: ", attributedict)         return type.__new__(cls, clsname, superclasses, attributedict)

А теперь используем метакласс LittleMeta в следующем примере:

class S:     pass   class A(S, metaclass=LittleMeta):     pass   a = A()
clsname:  A superclasses:  (<class '__main__.S'>,) attributedict:  {'__module__': '__main__', '__qualname__': 'A'}

Мы видим, что был вызван LittleMeta._new_, а не type._new_

Давайте определим метакласс EssentialAnswers, который может автоматически включать наш метод augmen_tanswer

 x = input("Do you need the answer? (y/n): ") if x.lower() == "y":     required = True else:     required = False       def the_answer(self, *args):                       return 42       class EssentialAnswers(type):          def __init__(cls, clsname, superclasses, attributedict):         if required:             cls.the_answer = the_answer                                  class Philosopher1(metaclass=EssentialAnswers):      pass   class Philosopher2(metaclass=EssentialAnswers):      pass   class Philosopher3(metaclass=EssentialAnswers):      pass           plato = Philosopher1() print(plato.the_answer())   kant = Philosopher2() # let's see what Kant has to say :-) print(kant.the_answer())
Do you need the answer? (y/n): y 42 42

В главе «Type and Class Relationship» мы выяснили, что после обработки определения класса Python вызывает:

type(classname, superclasses, attributes_dict)

Но не в том случае, когда метакласс был объявлен в заголовке. Именно так мы и сделали в нашем прошлом примере. Наши классы Philosopher1, Philosopher2 и Philosopher3 были «прицеплены» к метаклассу EssentialAnswers. И вот почему EssentialAnswers будет вызван вместо type:

EssentialAnswer(classname, superclasses, attributes_dict)

Если быть точным, то аргументам вызовов будет даны следующие значения:

EssentialAnswer('Philopsopher1',                  (),                  {'__module__': '__main__', '__qualname__': 'Philosopher1'})

Другие классы Philosopher будут вести себя аналогично.

Создаем синглтоны с помощью метаклассов

Синглтон  — это шаблон проектирования, который позволяет создать всего один экземпляр класса. Он используется в тех случаях, когда нужен ровно один объект. Понятие может быть обобщено, то есть мы можем ограничить создание экземпляров класса определенным или фиксированным количеством. Сам термин пришел к нам из математики, где синглтон, также называемый единичным множеством, используется для обозначения множества с всего одним элементом.

class Singleton(type):     _instances = {}     def __call__(cls, *args, **kwargs):         if cls not in cls._instances:             cls._instances[cls] = super(Singleton,                                      cls).__call__(*args, **kwargs)         return cls._instances[cls]           class SingletonClass(metaclass=Singleton):     pass   class RegularClass():     pass   x = SingletonClass() y = SingletonClass() print(x == y)   x = RegularClass() y = RegularClass() print(x == y)
True False

Также мы можем создавать Singleton-классы, наследуясь от Singleton, который можно определить следующим образом:

class Singleton(object):     _instance = None     def __new__(cls, *args, **kwargs):         if not cls._instance:             cls._instance = object.__new__(cls, *args, **kwargs)         return cls._instance       class SingletonClass(Singleton):     pass  class RegularClass():     pass   x = SingletonClass() y = SingletonClass() print(x == y)   x = RegularClass() y = RegularClass() print(x == y)
True False

Узнать подробнее о курсе.


Читать ещё:

ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/527384/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *