«Метаклассы – это более глубокая магия, о которой 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 теперь вы можете использовать класс-декораторы для достижения того, для чего раньше вы могли использовать метаклассы.