что такое метаклассы python
Использование метаклассов в Python
Некоторые средства метапрограммирования не так часто используются в ежедневной
работе, как обычные в ООП классы или те же декораторы. Для понимания же целей
введения подобных средств в язык требуются конкретные примеры промышленного
применения, некоторые из которых и приведены ниже.
Введение в метаклассы
Питон расширяет классическую парадигму, и сами классы в нем тоже становятся
равноправными объектами, которые можно менять, присваивать переменной и
передавать в функции. Но если класс — объект, то какому классу он соответствует?
По умолчанию этот класс (метакласс) называется type.
Простой пример
Предположим, нас утомило задание атрибутов в контрукторе __init__(self, *args,
**kwargs). Хотелось бы ускорить этот процесс таким образом, чтобы была
возможность задавать атрибуты прямо при создании объекта класса. С обычным
классом такое не пройдет:
Объект конструируется вызовом класса оператором «()». Создадим наследованием от
type метакласс, переопределяющий этот оператор:
Теперь создадим класс, использующий новый метакласс:
Расширение языка (абстрактные классы)
Ядро Python сравнительно небольшое и простое, набор встроенного инструментария
мал, что позволяет разработчикам быстро осваивать язык.
Вместе с тем, программистам, занимающимся созданием, например, фреймворков и
сопутствующих специальных подъязыков (Domain Specific Languages), предоставляются
достаточно гибкие инструменты.
Абстрактные классы (или их несколько иная форма — интерфейсы) — распространенный
и популярный среди программистов метод определения интерфейсной части
класса. Обычно такие понятия закладываются в ядро языка (как в Java или C++),
Питон же позволяет изящно и легко реализовать их собственными средствами, в
частности — при помощи метаклассов и декораторов.
Рассмотрим работу библиотеки abc из предложения по реализации для стандартной библиотеки.
Использовать асбтрактные классы очень легко. Создадим абстрактный базовый класс
с виртуальным методом и попробуем создать класс-наследник без определения этого метода:
Не вышло. Теперь определим нужный метод:
Узнаем, как это реализуется в метаклассе (опустив некоторые другие возможности
модуля abc) ABCMeta:
Метод _fix_bases добавляет скрытый класс _Abstract в число предков
абстрактного класса. Сам _Abstract проверяет, осталось ли что-нибудь во
множестве(set) __abstractmethods__; если осталось — выкидывает исключение.
В каждом абстрактном классе хранится по «замороженному» множеству(frozenset)
абстрактных методов; то есть тех методов (функций-объектов), у которых есть
атрибут __isabstractmethod__, выставляемый соответствующим декоратором:
Итак, абстрактный метод получает атрибут __isabstractmethod__ при назначении ему
декоратора. Атрибуты после наследования от абстрактного класса собираются во
множестве «__abstractmethods__» класса-наследника. Если множество не пустое, и
программист пытается создать объект класса, то будет вызвано исключение
TypeError со списком неопределенных методов.
Вывод
Просто? Просто. Язык расширен? Расширен. Комментарии, как говорится, излишни.
DSL в Django
Один из продвинутых примеров DSL — механизм ORM Django на примере класса Model и
метакласса ModelBase. Конкретно связь с базой данный здесь не интересны, имеет
смысл сконцентрироваться на создании экземпляра класса-наследника класса Model.
Большая часть следующего подраздела — подробный разбор кода
ModelBase. Читателям, не нуждающимся в подробностях, достаточно прочитать вывод
в конце раздела «Django».
Разбор метакласса ModelBase
Вся механика работы метакласса ModelBase сконцентрирована в месте
переопределения метода __new__, вызываемого непосредственно перед созданием
экземпляра класса модели:
В самом начале метода просто создается экземпляр класса и, если этот класс не
наследует от Model, просто возращается.
Все конкретные опции класса модели собираются в атрибуте класса _meta, который
может быть создан с нуля, унаследоваться от предка или быть подкорректирован в
локальном классе Meta:
Кроме того, видим, что класс может быть абстрактным, не соответствующим
какой-либо таблице в базе данных.
Момент истины в процессе создания класса модели наступает при внесении в него
параметров по умолчанию:
add_to_class либо вызывает метод contribute_to_class аргумента, либо, если
такового нет, просто добавляет именованный атрибут классу.
Класс же Options в своем contribute_to_class делает атрибут _meta ссылкой на
самого себя и собирает в нем различные параметры, вроде названия таблицы базы
данных, списка полей модели, списка виртуальных полей модели, прав доступа и
других. Он также проводит проверки связей с другими моделями на уникальность
названий полей в БД.
Далее в методе __new__ неабстрактному классу добавляются именованные
исключения:
Если класс-родитель — не абстрактный, и параметры не установлены явно в локальном
классе Meta, то наследуем параметры ordering и get_latest_by:
Менеджер по умолчанию должен быть нулевым. Если такая модель уже существует — завершаем обработку, возвращая эту модель:
Ничего особенного, просто добавляются в класс модели атрибуты, с которыми он был
создан:
Теперь требуется пройтись по полям модели и найти связи типа «один к одному»,
которые будут использовать чуть ниже:
Проход по предкам модели для наследования различных полей, с отбрасыванием тех,
что не являются наследниками Model. Далее переведены комментарии, которых
достаточно для понимания происходящего:
Абстрактные классы моделей нигде не регистрируются:
Нормальные же регистрируются и возращаются уже из списка зарегистрированных
классов моделей:
Вывод
Итак, подведем итоги. Зачем понадобились метаклассы?
1) Класс-модель должен иметь набор обязательных параметров (имя таблицы, имя
джанго-приложения, список полей, связи с другими моделями и многие другие) в
атрибуте _meta, которые и определяются при создании каждого класса, наследующего
от Model.
2) Эти параметры сложным образом наследуются от обычных и абстрактных
классов-предков, что некрасиво закладывать в сам класс.
3) Появляется возможность спрятать происходящее от программиста, использующего
фреймворк.
Замечаньица
1) Если явно не указывать наследование класса от object, то класс использует
метакласс, указанный в глобальной переменной __metaclass__, что иногда может
быть удобно при многократном использовании собственного метакласса в пределах
одного модуля. Простой пример, приведенный в начале заметки, можно переделать
следующим образом:
2) Есть такой супергуру питоновский, Тим Питерс. Он очень удачно сказал про
применение метаклассов и аналогичных средств из разряда черной магии Питона:
На русском это примерно так звучит:
Мораль тут простая: не мудрите. Метаклассы в большинстве случаев — лишнее. Питонист должен руководствоваться принципом наименьшего удивления;
менять классическую схему работы ООП не стоит просто ради самолюбования.
Ссылочки по мотивам
Английская Википедия — отсюда позаимствован простой примерчик
PEP-3119 — здесь
описываются абстрактные классы в полном своем варианте.
Ролик
на английском, подробный разговор про метаклассы в Питоне с примерами
использования. Там по ссылкам можно найти и саму статью с примерами, очень
поучительно.
Метаклассы в Python
Метаклассы – это такие классы, экземпляры которых сами являются классами. Подобно тому, как «обычный» класс определяет поведение экземпляров класса, метакласс определяет и поведение классов, и поведение их экземпляров.
Метаклассы поддерживаются не всеми объектно-ориентированными языками программирования. Те языки программирования, которые их поддерживают, значительно отличаются по способу их реализации. Но в Python метаклассы есть.
Некоторые программисты рассматривают метаклассы в Python как «решения, которые ждут или ищут задачу».
У метаклассов множество применений. Выделим несколько из них:
Логирование и профилирование;
Регистрация классов во время создания;
Автоматическое создание свойств;
Автоматическая блокировка/синхронизация ресурсов.
Определение метаклассов
Давайте создадим совсем простой метакласс. Он ничего не умеет, кроме вывода содержимого своих аргументов в методе _new_ и возврата результата вызова type._new_ :
А теперь используем метакласс LittleMeta в следующем примере:
В главе «Type and Class Relationship» мы выяснили, что после обработки определения класса Python вызывает:
Но не в том случае, когда метакласс был объявлен в заголовке. Именно так мы и сделали в нашем прошлом примере. Наши классы Philosopher1, Philosopher2 и Philosopher3 были «прицеплены» к метаклассу EssentialAnswers. И вот почему EssentialAnswers будет вызван вместо type:
Если быть точным, то аргументам вызовов будет даны следующие значения:
Другие классы Philosopher будут вести себя аналогично.
Создаем синглтоны с помощью метаклассов
Также мы можем создавать Singleton-классы, наследуясь от Singleton, который можно определить следующим образом:
Продвинутый Python, часть 3: классы и метаклассы
Это завершающая статья цикла «Продвинутый Python», в которой пойдёт речь о классах и метаклассах. В первой части мы познакомились с итераторами, генераторами и модулем itertools, а во второй говорили о замыканиях, декораторах и модуле functools.
Классы как объекты
Посмотрите пример кода, чтобы ближе познакомиться с описанными особенностями.
В некоторых языках, таких как C++, классы можно объявлять только на верхнем уровне модулей. В Python class можно использовать внутри функции. Этот подход можно использовать, чтобы создавать классы на лету. Ниже пример:
Метаклассы
Обратите внимание, второй параметр должен быть кортежем, поэтому синтаксис может выглядеть странно. Если вам нужны методы, создавайте функции и передавайте их как атрибуты. Вот пример:
Можно создавать свои метаклассы: сгодится любой вызываемый (callable) объект, который способен принять три параметра и вернуть объект класса. Такие метаклассы можно применять к классу. Метакласс можно указать при объявлении класса. Давайте рассмотрим этот приём на примере, который заодно продемонстрирует возможности метаклассов:
Как видите, у декораторов и метаклассов есть много общего. Фактически, метаклассы умеют всё, что можно сделать с помощью декоратора класса. Синтаксис декораторов более простой и читабельный, поэтому по возможности следует использовать именно их. Метаклассы умеют больше, так как они запускаются перед созданием класса, а не после, как декораторы. Чтобы убедиться в этом, давайте создадим декоратор и метакласс и посмотрим на порядок исполнения.
Изучайте Python на Хекслете Первые курсы в профессии «Python-программист» доступны бесплатно. Регистрируйтесь и начинайте учиться!
Пример использования метаклассов
Рассмотрим более полезное приложение. Предположим, мы пишем набор классов для обработки ID3v2 тегов, которые используются, например, в MP3-файлах. Подробности можно узнать в «Википедии». Для реализации примера надо понимать, что теги состоят из фреймов. Каждый фрейм содержит четырёхбуквенный идентификатор. Например, TOPE — фрейм имени артиста, TOAL — фрейм названия альбома и так далее. Предположим, нам надо написать класс для каждого типа фреймов. Также нужно дать возможность пользователям библиотеки ID3v2 тегов добавлять собственные классы фреймов для поддержки новых или кастомных фреймов. С помощью метаклассов можно реализовать паттерн «фабрика классов». Это может выглядеть так:
Конечно, задачу можно решить с помощью декораторов классов. Для сравнения посмотрите, как это может выглядеть.
Как видите, можно передавать параметры в декораторы, но не в метаклассы. Если нужно передать параметры в метаклассы, это нужно делать через атрибуты. Поэтому код с декораторами чище и проще в поддержке. Заметьте, что ко времени вызова декоратора класс уже создан. Это значит, что уже поздно менять его свойства, предназначенные только для чтения.
Метаклассы, полученные из type
Подводим итоги
Посмотрим, как Python интерпретирует код выше. Затем посмотрим на вывод, чтобы подтвердить или опровергнуть наши предположения.
Метаклассы на практике
ABCMeta — это метакласс, позволяющий создавать абстрактные базовые классы. Детали смотрите в официальной документации.
Идея djungoplugins основана на статье, в которой описывается простой фреймворк плагинов для Python. Здесь метаклассы используются для создания системы расширений. Автор оригинальной публикации считает, что такой же фреймворк можно создать с помощью декораторов.
Финальный аккорд
Понимание метаклассов помогает досконально разобраться, как ведут себя объекты и классы в Python. Но применение самих метаклассов в реальности может быть сложным, как показано в предыдущем разделе. Практически всё, что можно сделать с помощью метаклассов, можно реализовать и с помощью декораторов. Поэтому прежде чем использовать метаклассы, остановитесь на минуту и подумайте, так ли они необходимы. Если можно обойтись без них, лучше пойти по этому пути. Результат будет более читабельным и простым для поддержки и отладки.
Над адаптированным переводом статьи A Study of Python’s More Advanced Features Part III: Classes and Metaclasses by Sahand Saba работали Алексей Пирогов и Дмитрий Дементий. Мнение автора оригинальной публикации может не совпадать с мнением администрации «Хекслета».
Что такое метаклассы в Python?
Что такое метаклассы в Python и для чего нужно их использовать?
1 ответ 1
Классы как объекты
Прежде чем разбираться в метаклассах, нужно пройти мастер-классы по Python. Python имеет очень своеобразное представление о том, что такое классы, заимствованное из языка Smalltalk.
Но в Python классы это нечто большее. Классы это тоже объекты.
Этот объект (класс) сам по себе может создавать объекты (экземпляры), и поэтому он является классом.
Но все же это объект, а значит
Динамическое создание классов
Поскольку классы являются объектами, вы можете создавать их на лету, как и любой объект.
Во-первых, вы можете создать класс в функции, используя class
Но это не так динамично, так как вам все равно придется писать весь класс самостоятельно.
Поскольку классы являются объектами, они должны быть созданы чем-то.
Что ж, у type есть совершенно другие возможности, он также может создавать классы на лету. type может принимать описание класса как параметры и возвращать класс
(Я знаю, глупо, что одна и та же функция может иметь два совершенно разных использования в зависимости от параметров, которые вы ей передаете. Это проблема из-за обратной совместимости в Python)
type работает следующим образом:
можно создать вручную следующим образом:
Вы заметите, что мы используем «MyShinyClass» как имя класса и как переменную для хранения ссылки на класс. Они могут быть разными, но нет причин усложнять ситуацию.
type принимает словарь для определения атрибутов класса
Можно превратить в:
И использовать как обычный класс:
И, конечно, вы можете наследовать от него:
В конце концов, вы захотите добавить методы в свой класс. Просто определите функцию с соответствующей сигнатурой и назначьте ее как атрибут.
После динамического создания класса вы можете добавить еще больше методов, точно так же, как добавление методов к нормально созданному объекту класса
Что такое метаклассы
Вы определяете классы для создания объектов, верно?
Что ж, метаклассы создают эти объекты. Это классы классов, вы можете изобразить их так:
Вы видели, что type позволяет делать что-то вроде этого:
Все, абсолютно все, в Python является объектом. Сюда входят целые числа, строки, функции и классы. Все они объекты. И все они созданы из класса:
Если хотите, можете назвать это «фабрикой классов».
Атрибут metaclass
Метаклассы в Python 2
В Python 2 вы можете добавить атрибут __metaclass__ при написании класса (синтаксис Python 3 см. В следующем разделе):
Осторожно, это сложно.
Прочтите это несколько раз
Когда вы сделаете это:
Python делает следующее:
А что может создать класс? type или что-либо, что его подклассы используют
Метаклассы в Python 3
Синтаксис для установки метакласса был изменен в Python 3:
Т.е. атрибут __metaclass__ больше не используется, а был превращен в аргумент в списке родительских классов
Однако поведение метаклассов в основном остается неизменным.
Одна вещь, добавленная к метаклассам в Python 3, заключается в том, что вы также можете передавать атрибуты как ключевые слова-аргументы в метакласс, например:
Прочтите раздел ниже, чтобы узнать, как Python справляется с этим.
Пользовательские метаклассы
Основная цель метакласса, автоматически изменять класс при его создании.
Обычно вы делаете это для API, где хотите создавать классы, соответствующие текущему контексту.
Таким образом, все классы этого модуля будут созданы с использованием этого метакласса, и нам просто нужно указать метаклассу перевести все атрибуты в верхний регистр.
К счастью, __metaclass__ на самом деле может быть любым вызываемым объектом, он не обязательно должен быть формальным классом (я знаю, что что-то с «class» в его имени не обязательно должно быть классом, поймите… это полезно).
Итак, мы начнем с простого примера, используя функцию.
Теперь сделаем то же самое, но с использованием реального класса для метакласса:
Давайте перепишем приведенное выше, но с более короткими и более реалистичными именами переменных, теперь, когда мы знаем, что они означают:
Вот и все. Больше в метаклассах больше ничего нет.
Причина сложности кода с использованием метаклассов заключается не в метаклассах, а в том, что вы обычно используете метаклассы для выполнения извращенных вещей, основанных на интроспекции, манипулировании наследованием, переменными, такими как __dict__ и т.д.
В самом деле, метаклассы особенно полезны для черной магии и, следовательно, для сложных вещей. Но сами по себе они просты:
Зачем для метаклассов использовать классы, а не функций?
Поскольку metaclass может принимать любые вызываемые объекты, зачем использовать класс, если он явно более сложен?
Для этого есть несколько причин:
Вы можете использовать ООП. Метакласс может наследоватся от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
Подклассы класса будут экземплярами его метакласса, если вы укажите метакласс с помощью класса, а не с помощью функции.
Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как приведенный выше пример. Обычно это для чего-то сложного. Возможность создавать несколько методов и группировать их в один класс очень полезно для облегчения чтения кода.
Они называются метаклассами, черт возьми! Это должно что-то значить!
Зачем использовать метаклассы?
А теперь большой вопрос. Зачем использовать какую-то непонятную функцию, подверженную ошибкам?
Ну, обычно вы этого не делаете:
Python гуру Tim Peters.
Но если вы сделаете это:
Django делает что-то сложное простым, предоставляя простой API и используя метаклассы, воссоздавая код из этого API, чтобы делать реальную работу за кулисами.
Последнее слово
На самом деле классы сами по себе являются экземплярами метаклассов.
В Python все является объектом, и все они являются экземплярами классов или экземплярами метаклассов.
type на самом деле является отдельным метаклассом. Это не то, что вы могли бы воспроизвести на чистом Python, это делается путем небольшого мошенничества на уровне реализации.
Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простых изменений класса. Вы можете менять классы, используя два разных методы:
В 99% случаев, когда вам нужно изменить класс, вам лучше использовать их.
Но в 98% случаев вам вообще не нужно менять класс.
Метаклассы в Python
Как сказал один из пользователей StackOverflow, «using SO is like doing lookups with a hashtable instead of a linked list». Мы снова обращаемся к этому замечательному ресурсу, на котором попадаются чрезвычайно подробные и понятные ответы на самые различные вопросы.
В этот раз мы обсудим, что такое метаклассы, как, где и зачем их использовать, а также почему обычно этого делать не стоит.
Классы как объекты
Перед тем, как изучать метаклассы, надо хорошо разобраться с классами, а классы в Питоне — вещь весьма специфическая (основаны на идеях из языка Smalltalk).
В большинстве языков класс это просто кусок кода, описывающий, как создать объект. В целом это верно и для Питона:
Но в Питоне класс это нечто большее — классы также являются объектами.
Этот объект (класс) сам может создавать объекты (экземпляры), поэтому он и является классом.
Динамическое создание классов
Так как классы являются объектами, их можно создавать на ходу, как и любой объект.
Например, можно создать класс в функции, используя ключевое слово class :
Однако это не очень-то динамично, поскольку по-прежнему нужно самому писать весь класс целиком.
Поскольку классы являются объектами, они должны генерироваться чем-нибудь.
На самом деле, у функции type есть совершенно иное применение: она также может создавать классы на ходу. type принимает на вход описание класса и созвращает класс.
(Я знаю, это по-дурацки, что одна и та же функция может использоваться для двух совершенно разных вещей в зависимости от передаваемых аргументов. Так сделано для обратной совместимости)
type работает следующим образом:
может быть создан вручную следующим образом:
Возможно, вы заметили, что мы используем «MyShinyClass» и как имя класса, и как имя для переменной, содержащей ссылку на класс. Они могут быть различны, но зачем усложнять?
type принимает словарь, определяющий атрибуты класса:
можно переписать как
и использовать как обычный класс
Конечно, можно от него наследовать:
В какой-то момент вам захочется добавить методов вашему классу. Для этого просто определите функцию с нужной сигнатурой и присвойте её в качестве атрибута:
Уже понятно, к чему я клоню: в Питоне классы являются объектами и можно создавать классы на ходу.
Что такое метакласс (наконец)
Метакласс это «штука», которая создаёт классы.
Мы создаём класс для того, чтобы создавать объекты, так? А классы являются объектами. Метакласс это то, что создаёт эти самые объекты. Они являются классами классов, можно представить это себе следующим образом:
Мы уже видели, что type позволяет делать что-то в таком духе:
Это потому что функция type на самом деле является метаклассом. type это метакласс, который Питон внутренне использует для создания всех классов.
Это легко проверить с помощью атрибута __class__ :
В питоне всё (вообще всё!) является объектами. В том числе числа, строки, функции и классы — они все являются объектами и все были созданы из класса:
Итак, метакласс это просто штука, создающая объекты-классы.
Если хотите, можно называть его «фабрикой классов»
type это встроенный метакласс, который использует Питон, но вы, конечно, можете создать свой.
Атрибут __metaclass__
При написании класса можно добавить атрибут __metaclass__ :
Осторожно, тут есть тонкость!
То есть когда вы пишете
Питон делает следующее:
Если же __metaclass__ не находится ни в одном из родителей, Питон будет искать __metaclass__ на уровне модуля.
Ответ: что-нибудь, что может создавать классы.
А что создаёт классы? type или любой его подкласс, а также всё, что использует их.
Пользовательские метаклассы
Основная цель метаклассов — автоматически изменять класс в момент создания.
Обычно это делает для API, когда хочется создавать классы в соответсвии с текущим контекстом.
Представим глупый пример: вы решили, что у всех классов в вашем модуле имена атрибутов должны быть записать в верхнем регистре. Есть несколько способов это сделать, но один из них — задать __metaclass__ на уровне модуля.
В таком случае все классы этого модуля будут создаваться с использованием указанного меакласса, а нам остаётся только заставить метакласса переводить имена всех атрибутов в верхний регистр.
К счастью, __metaclass__ может быть любым вызываемым объектом, не обязательно формальным классом (я знаю, что-то со словом «класс» в названии не обязано быть классом, что за ерунда? Однако это полезно).
Так что мы начнём с простого примера, используя функцию.
А теперь то же самое, только используя настояший класс:
Но это не совсем ООП. Мы напрямую вызываем type и не перегружаем вызов __new__ родителя. Давайте сделаем это:
Вот и всё. О метаклассах больше ничего и не сказать.
Причина сложности кода, использующего метаклассы, не в самих метаклассах. Она в том, что обычно метаклассы используются для всяких изощрённых вещей, основанных на интроспекции, манипуляцией наследованием, переменными вроде __dict__ и тому подобном.
Зачем использовать метаклассы вместо функций?
Поскольку __metaclass__ принимает любой вызываемый объект, с чего бы вдруг использовать класс, если это очевидно сложнее?
Зачем вообще использовать метаклассы?
Наконец, главный вопрос. С чего кому-то использовать какую-то непонятную (и способствующую ошибкам) фичу?
Ну, обычно и не надо использовать:
Метаклассы это глубокая магия, о которой 99% пользователей даже не нужно задумываться. Если вы думаете, нужно ли вам их использовать — вам не нужно (люди, которым они реально нужны, точно знают, зачем они им, и не нуждаются в объяснениях, почему).
Гуру Питона Тим Питерс
Основное применение метаклассов это создание API. Типичный пример — Django ORM.
Она позволяет написать что-то в таком духе:
Однако если вы выполните следующий код:
Django делает что-то сложное выглядящим простым, выставляя наружу простое API и используя метаклассы, воссоздающие код из API и незаметно делающие всю работу.
Напоследок
ВО-первых, вы узнали, что классы это объекты, которые могут создавать экземпляры.
На самом деле, классы это тоже экземпляры. Экземпляры метаклассов.
Всё что угодно является объектом в Питоне: экземпляром класса или экземпляром метакласса.
type является собственным метаклассом. Это нельзя воспроизвести на чистом Питоне и делается небольшим читерством на уровне реализации.
Но в 99% случаев вам вообще не нужно изменять классы 🙂