Урок 44 из 84 · ООП
Метаклассы
Содержание урока
Python — это объектно-ориентированный язык, который облегчает работу с классами. Метапрограммирование в Python основано на новом особом типе классов, которые называются метаклассами.
Метаклассы – это классы, экземпляры которых являются классами.

Когда мы определяем класс, его объекты создаются, используя класс как пример. Но как насчёт самого класса? Что есть этот самый пример для класса?
Здесь на сцену выходит метакласс. Метакласс — это пример самого класса, как класс есть пример для его экземпляров. Метакласс — это класс, который определяет свойства других классов. С помощью метакласса мы можем определять свойства, которые следует добавить к новым классам, определяемым в нашем коде.
Например, метакласс в следующем образце кода добавляет свойство hello к каждому классу, использующему этот метакласс как шаблон. Это значит, что новые классы — экземпляры этого метакласса будут иметь свойство hello без необходимости определять его отдельно:
# hello_metaclass.py
# Простой метакласс
# Этот метакласс добавляет метод 'hello' к классам, использующим его значение,
# те классы получают метод 'hello' без лишних усилий
# метакласс заботится о генерации кода для нас
class HelloMeta(type):
# Метод hello
def hello(cls):
print("greetings from %s, a HelloMeta type class" % (type(cls())))
# Вызываем метакласс
def __call__(self, *args, **kwargs):
# создаём новый класс как обычно
cls = type.__call__(self, *args)
# определяем новый метод hello для каждого из этих классов
setattr(cls, "hello", self.hello)
# возвращаем класс
return cls
# Проверяем метакласс
class TryHello(object, metaclass=HelloMeta):
def greet(self):
self.hello()
# Создаём экземпляр метакласса. Он должен автоматически содержать метод hello
# хотя он не объявлен в классе вручную
# иными словами, он объявлен за нас метаклассом
greeter = TryHello()
greeter.greet()
В результате запуска этого кода новый класс TryHello способен напечатать приветствие:
greetings from <class '__main__.TryHello'>, a HelloMeta type class
Метод, ответственный за этот вывод, не объявлен в декларации класса. Вместо этого метакласс, в данном случае HelloMeta, порождает код во время запуска, что сразу связывает этот метод с классом.
Чтобы увидеть это в действии, смело копируйте код в консоль Python.
Также прочтите комментарии, чтобы лучше понимать, что мы сделали в каждой части кода. У нас есть новый объект по имени greeter, который является сущностью класса TryHello. Впрочем, мы можем вызвать метод self.hello класса TryHello, хотя этот метод не определён в объявлении класса TryHello.
Вместо того, чтобы выдать ошибку вызова несуществующего метода, TryHello автоматически привязывает этот метод к классу, используя HelloMeta как его метакласс.
Метаклассы дают нам возможность писать код, который изменяет не только данные, но и другой код, то есть изменяет класс во время его создания. В примере выше наш метакласс автоматически добавляет новый метод к новым классам, которые мы определяем, чтобы использовать метакласс.
Это пример метапрограммирования. Метапрограммирование — это просто написание кода, который работает с метаклассами и схожие методики изменения кода на заднем плане.
Прекрасно в метапрограммировании то, что вместо вывода исходного кода, оно даёт нам лишь исполнение этого кода. Пользователь нашей программы не знает о «магии», происходящей за кадром.
Подумайте о фреймворках, которые порождают код в фоновом режиме, чтобы убедиться, что вам как программисту нужно писать меньше кода для всего. Вот несколько очень хороших примеров:
- Django
- SQLAlchemy
- Flask
Метапрограммирование используется авторами фреймворков, чтобы неявно порождать код и заботиться обо всем. В результате получаем упрощённые пользовательские API, которые автоматизируют много работы за программиста, пишущего код фреймворка.
Обеспечить, чтобы простота работала за кадром — как раз то, для чего метапрограммирование встроено в исходный код фреймворков.
Немного теории: разберёмся, как работают метаклассы
Чтобы понять, как работают метаклассы в Python, Вам нужно очень хорошо понимать нотацию типов. Тип — это просто номенклатура данных или объекта в Python.
Получение типа объекта
Используя Python REPL (командный интерпретатор), давайте создадим простой строковый объект и проверим его тип:
>>> day = "Sunday"
>>> print("The type of variable day is %s" % (type(day)))
The type of variable day is <type 'str'>
Как и следовало ожидать, мы получили вывод, что переменная day относится к типу str, строковому типу. Вы можете найти тип любого объекта, просто используя встроенную функцию type с одним аргументом-объектом.
Получение типа класса
Так, строка вроде "Sunday" или "hello" имеет тип str, но как насчёт самого str ? Какой тип у класса str ?
Опять введём в консоль Python:
>>> type(str)
<type 'type'>
В этот раз мы получаем вывод: str принадлежит типу type.
Тип и тип его типа
Что же насчет самого type? Каков тип type?
>>> type(type)
<type 'type'>
В результате снова получаем «type». Так мы находим, что type есть не только метакласс для типов наподобие int, но и свой собственный метакласс!
Специальные методы, которые используются метаклассами
Здесь нам поможет немного теории. Вспомним, что метакласс — это класс, сущностями которого являются сами классы, а не просто обычные объекты. В Python 3 можно назначить метакласс при создании нового класса, передав главный класс в определение нового класса.
Тип type, как метакласс по умолчанию в Python, определяет особые методы, которые новый метакласс может переписать, чтобы создать поведение уникального кода. Вот краткий обзор этих «волшебных» методов, существующих в метаклассе:
- __new__: этот метод вызывается в метаклассе до того, как создаётся сущность класса, на нём основанного
- __init__: этот метод вызывается, чтобы установить переменные после создания сущности/объекта
- __prepare__: определяет пространство имён класса в отображении, сохраняющием атрибуты
- __call__: этот метод вызывается, когда конструктор нового класса нужно использовать для создания оъекта
Вот методы, переопределение которых в вашем метаклассе даст вашим классам поведение, отличное от типа type метакласса по умолчанию.