Метаклассы

«Метаклассы – это более глубокая магия, о которой 99% пользователям не стоит беспокоиться. Если вы задумываетесь, нужны ли они вам, то скорее всего – нет».

Тим Питерс

Метаклассы имеют репутацию «глубокой черной магии» в Python. Случаи, когда вы в них нуждаетесь, действительно редки (если вы не программируете с Zope …), но основные принципы удивительно легко понять.

Всё является Объектом

  • Все является объектом
  • Все имеет тип
  • Никакой реальной разницы между ‘классом’ и ‘типом’
  • Классы являются объектами
  • Их тип – type

Как правило, термин «тип» используется для встроенных типов и термина «класс» для пользовательских классов. Начиная с Python 2.2 не было никакой реальной разницы, и «класс» и «тип» являются синонимами.

Для классических (старых) классов их тип – types.ClassType.

На примере выше, мы видим, что класс, созданный в интерактивном интерпретаторе, является объектом первого класса.

Класс одного из классов …

Называется метакласс.
Подобно тому, как объект является экземпляром своего класса; класс является экземпляром своего метакласса.
Для создания класса вызывается метакласс.
Точно так же, как и любой другой объект в Python.

Итак, когда вы создаете класс …

Интерпретатор вызывает метакласс для его создания …

Для нормального класса, наследуемого от объекта, это означает, что для создания класса вызывается type:

Именно второе использование type является важным. Когда интерпретатор Python выполняет утверждение класса, он вызывает type со следующими аргументами:

  • Имя класса в виде строки
  • Кортеж базовых классов – для нашего примера это ”one-pl’ (object,)
  • Словарь, содержащий элементы класса (атрибуты класса, методы и т. д.), соответствующие их именам

Легкий пример


Этот код создает словарь атрибутов класса, а затем вызывает type для создания класса с именем Hello.

Магия __metaclass__

Мы можем предоставить настраиваемый метакласс, установив __metaclass__ в определении класса для любого вызываемого объекта, который принимает те же аргументы, что и type.

Обычный способ сделать это – позаимствовать у type:

Важно то, что внутри структуры метода __new__ мы имеем доступ к аргументам, переданным для создания нового класса. Мы можем просмотреть словарь атрибутов, а также модифицировать, добавлять или удалять элементы.

Важно переопределить __new__, а не __init__. Когда вы создаете экземпляр класса, вызываются как __init__, так и __new__. __init__ инициализирует экземпляр, но __new__ отвечает за его создание. Так что, если наш метакласс собирается настроить создание класса, нам нужно переопределить тип __new__.

Причина использования нового типа, а не только фабричной функции заключается в том, что если вы используете фабричную функцию (которая просто вызывает тип), метакласс не будет наследоваться.

В действии:

WhizzBang – это класс, но вместо того, чтобы быть экземпляром type, объект класса теперь является экземпляром нашего пользовательского метакласса.

Что мы можем с этим сделать?

Наш метакласс будет вызываться всякий раз, когда создается новый класс, который его использует. Вот некоторые идеи:

  • Декорирование  всех методов класса для реестра или профилирования.
  • Автоматическое смешивание новых методов.
  • Регистрирование классов по мере их создания. (Авто-регистрация плагинов или создание db схемы из членов класса, например.
  • Обеспечение регистрации интерфейса, автоматическое обнаружение функций и адаптация интерфейса.
  • Проверка класса: запрещение подклассификации, проверка, что все методы имеют док-строки.

Важным является то, что класс фактически создается только последним вызовом type в метаклассе, поэтому вы можете изменять словарь атрибутов по своему усмотрению (и имя плюс кортеж базовых классов, конечно).

Некоторые популярные Python ORM (Object Relational Mappers для работы с базами данных) используют метаклассы данными способами.

Однако

Скорее всего, вам не придется использовать его в производственном коде. Он может пригодится в профилировании или в Ironclad.
Конечно, все это относится только к Python 2.X. Для Python 3 механизм совершенно другой.

 type(type) is type

С Python 2.6 теперь вы можете использовать класс-декораторы для достижения того, для чего раньше вы могли использовать метаклассы.

Поделитесь с друзьями:

Оставьте комментарий