Метаклассы в Python: что это такое и с чем его едят

0
5250

Метаклассы – это классы, экземпляры которых являются классами. Давайте поговорим о специфике языка Python и его функционале.

метаклассы

Чтобы создать свой собственный метакласс в Python, нужно воспользоваться подклассом type, стандартным метаклассом в Python. Чаще всего метаклассы используются в роли виртуального конструктора. Чтобы создать экземпляр класса, нужно сначала вызвать этот самый класс. Точно так же делает и Python: для создания нового класса вызывает метакласс. Метаклассы определяются с помощью базовых классов в атрибуте __metaclass__. При создании класса допускается использование методов __init__ и __new__. С их помощью можно пользоваться дополнительными функциями. Во время выполнения оператора class генерируется пространство имен, которое будет содержать атрибуты будущего класса. Затем, для непосредственного создания, вызывается метакласс с именем и атрибутами.

Пример:

Классы как объекты

Перед тем как начинать разбираться в метаклассах, нужно хорошо понимать как работают обычные классы в Python, а они очень своеобразны. В большинстве языков это попросту фрагменты кода, которые описывают создание объекта. Данное суждение истинно и для Python:

Но есть один нюанс. Классы в Python это объекты. Когда выполняется оператор class, Python создает в памяти объект с именем ObjectCreator.

Объект способен сам создавать экземпляры, так что это класс. А объект вот почему:

  • его можно назначить в качестве переменной
  • копируется
  • есть возможность добавить к нему атрибуты
  • передается в роли параметра функции

Динамическое создание классов

Если классы в Python – это объекты, значит, как и любой другой объект, их можно создавать на ходу. Пример создания класса в функции с помощью class:

Но это не слишком-то динамично, так как все равно придется прописывать весь класс самостоятельно.

Исходя из того, что классы являются объектами, можно сделать вывод, что они должны чем-то генерироваться. Во время выполнения оператора class, Python автоматически создает этот объект, но есть возможность сделать это вручную.

Помните функцию type? Старая добрая функция, позволяющая определить тип объекта:

Эта функция может создавать классы на ходу. В качестве параметра type принимает описание класса, и возвращает класс.

Функция type работает следующим образом:

Например:

Можно создать вручную:

Вероятно, вы обратили внимание на то, что MyShinyClass выступает и в качестве имени класса, и в качестве переменной для хранения ссылок на класс.
type принимает словарь для определения атрибутов класса.

Можно написать как:

Используется как обычный класс:

Конечно же, его можно наследовать:

Получится:

Затем в свой класс нужно будет добавить методы. Для этого просто определите функцию с соответствующей сигнатурой и назначьте ее в качестве атрибута.

После динамического создания можно добавить еще методов:

В чем же суть? Классы в Python являются объектами, поэтому можно динамически создавать класс на ходу. Именно это и делает Python во время выполнения оператора class.

Что же такое метакласс?

Если говорить в двух словах, то метакласс – это «штуковина», создающая классы. Чтобы создавать объекты, мы определяем классы, правильно? Но мы узнали, что классы в Python являются объектами. На самом деле метаклассы – это то, что создает данные объекты. Довольно сложно объяснить. Лучше приведем пример:

Ранее уже упоминалось, что type позволяет делать что-то вроде этого:

Скорее всего, вы задаетесь вопросом: почему имя функции пишется с маленькой буквы?

Скорее всего, это вопрос соответствия со str – классом, который отвечает за создание строк, и int – классом, создающим целочисленные объекты. type – это просто класс, создающий объекты класса. Проверить можно с помощью атрибута __class__. Все, что вы видите в Python – объекты. В том числе и строки, числа, классы и функции. Все это объекты, и все они были созданы из класса:

Интересный вопрос: какой __class__ у каждого __class__?

Можно сделать вывод, что метакласс создает объекты класса. Это можно назвать «фабрикой классов». type – встроенный метакласс, который использует Python. Также можно создать свой собственный метакласс.

Атрибут __metaclass__

При написании класса можно добавить атрибут __metaclass__:

Если это сделать, то для создания класса Foo Python будет использовать метакласс.

СТОИТ ПОМНИТЬ!

Если написать class Foo(object), объект класса Foo не сразу создастся в памяти.
Python будет искать __metaclass__. Как только атрибут будет найден, он используется для создания класса Foo. В том случае, если этого не произойдет, Python будет использовать type для создания класса.

Если написать:

Python делает следующее:

Проверит, есть ли атрибут __metaclass__ у класса Foo? Если он есть, создаст в памяти объект класса с именем Foo с использованием того, что находится в __metaclass__.

Если Python вдруг не сможет найти __metaclass__, он будет искать этот атрибут на уровне модуля и после этого повторит процедуру. В случае если он вообще не может найти какой-либо __metaclass__, Python использует собственный метакласс type, чтобы создать объект класса.

Теперь вопрос: что можно добавить в __metaclass__?
Ответ: что угодно, что может создавать классы.

А что может создать класс? type или его подклассы, а также всё, что его использует.

Пользовательские метаклассы

Основной целью метакласса является автоматическое изменение класса во время его создания. Обычно это делается для API, когда нужно создать классы, соответствующие текущему контексту. Например, вы решили, что все классы в модуле должны иметь свои атрибуты, и они должны быть записаны в верхнем регистре. Чтобы решить эту задачу, можно задать __metaclass__ на уровне модуля.

Таким образом, нужно просто сообщить метаклассу, что все атрибуты должны быть в верхнем регистре. __metaclass__ действительно может быть любым вызываемым объектом, он не обязательно должен быть формальным классом. Итак, начнем с простого примера, с использованием функции.

Теперь то же самое, но с использованием метакласса:

Но это не совсем ООП, так как type не переопределяется, а вызывается напрямую. Давайте реализуем это:

Скорее всего, вы заметили дополнительный аргумент upperattr_metaclass. В нём нет ничего особенного: этот метод первым аргументом получает текущий экземпляр. Точно так же, как и self для обычных методов. Имена аргументов такие длинные для наглядности, но для self все имена имеют названия обычной длины. Поэтому реальный метакласс будет выглядеть так:

Используя метод super, можно сделать код более “чистым”:

Вот и все. О метаклассах больше рассказать нечего.

Причина сложности кода, использующего метаклассы, заключается не в самих метаклассах. Код сложный потому, что обычно метаклассы используются для сложных задач, основанных на наследовании, интроспекции и манипуляции такими переменными, как __dict__. Также с помощью метаклассов можно:

  • перехватить создание класса
  • изменить класс
  • вернуть измененный класс

Зачем использовать классы метаклассов вместо функций?

Есть несколько причин для этого:

  • Более понятные названия. Когда вы читаете UpperAttrMetaclass(type), вы знаете что будет дальше.
  • Вы можете использовать ООП. Метакласс может наследоваться от метакласса, переопределять родительские методы.
  • Можно лучше структурировать свой код. Вряд ли вы будете использовать метаклассы для чего-то простого. Обычно это более сложные задачи. Возможность создавать несколько методов и группировать их в одном классе очень полезна, чтобы сделать код более удобным для чтения.
  • Можете использовать __new__, __init__ и __call__. Это открывает простор для творчества. Обычно все это можно сделать в __new__, но некоторым людям просто удобнее работать в __init__.

Зачем использовать метаклассы?

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

~ Гуру Python Tim Peters

В основном метаклассы используются для создания API. Типичным примером является Django ORM. Можно написать что-то вроде этого:

Но если написать так:

Он не вернет объект IntegerField. Он вернет код int и даже может взять его непосредственно из базы данных.

Последнее слово

Во-первых, классы – это объекты, создающие экземпляры. Классы сами являются экземплярами метаклассов.

В Python все является объектами. Все они являются либо экземплярами классов, либо экземплярами метаклассов. За исключением type. type – сам себе метакласс. Его невозможно создать в чистом Python, это можно сделать только при помощи небольшого читерства.

Во-вторых, метаклассы сложны. Если вам не нужны сложные изменения класса, метаклассы использовать не стоит. Просто изменить класс можно двумя способами:

  • Руками
  • Декораторами класса

В 99% случаев лучше использовать эти методы, а в 98% изменения класса вообще не нужны.

Оригинал

Материалы по теме: