для чего нужны метаклассы python

Метаклассы в Python

Все мы знаем, что python – это объектно-ориентированный язык программирования, что означает, что он связывает данные с функциями, то есть классами.

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

Встроенный метакласс

type – это встроенный метакласс в Python. Начнем со следующего примера:

В приведенном выше коде мы создали класс с именем SampleClass и создали из него объект.

Это означает, что obj является объектом SampleClass. Увидеть тип самого SampleClass можно следующим образом:

Это означает, что SampleClass является объектом типа класса. Чтобы быть более конкретным, SampleClass – это экземпляр типа класса, т.е. метакласс. Каждый класс принадлежит к типу встроенного метакласса.

Как работает метакласс?

Каждый раз, когда мы создаем какой-либо класс, вызывается метакласс по умолчанию. Он вызывается с тремя данными – именем класса, набором базовых классов и атрибутами класса.

Поскольку тип – это встроенный метакласс, всякий раз, когда мы создаем класс, тип вызывается с этими тремя аргументами.

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

Как создать класс с помощью метакласса?

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

Это создаст класс с именем Student во время выполнения кода. Вышеприведенная строка эквивалентна следующему коду:

Если наследует какой-то другой класс, скажем, например, Department, тогда мы напишем следующее:

Унаследованные классы должны быть указаны во втором аргументе при создании класса на лету:

Если класс Student содержит некоторые атрибуты и функции, то они должны быть указаны в третьем аргументе как пара значений ключа. Смотрите следующие примеры:

Эти атрибуты и функции могут быть добавлены следующим образом:

Обратите внимание, что мы определили функцию, прежде чем использовать ее. Я хочу, чтобы вы поняли еще одно: первый аргумент – это имя класса. Итак, если вы напишете следующее:

Итак, лучше сохранить одно и то же имя для класса и переменной, чтобы сохранить согласованность.

Как установить?

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

Он будет производить вывод как:

Создание

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

Для этого класс должен наследовать тип метакласса по умолчанию, поскольку он является основным. Смотрите следующий пример:

Источник

Метаклассы в Python

для чего нужны метаклассы python

Привет, Хабр! У нас продолжается распродажа в честь черной пятницы. Там вы найдете много занимательных книг.

Возможен вопрос: а что такое метакласс? Если коротко, метакласс относится к классу точно как класс к объекту.

Метаклассы – не самый популярный аспект языка Python; не сказать, что о них воспоминают в каждой беседе. Тем не менее, они используется в весьма многих статусных проектах: в частности, Django ORM[2], стандартная библиотека абстрактных базовых классов (ABC)[3] и реализации Protocol Buffers [4].

Это сложная фича, позволяющая программисту приспособить под задачу некоторые самые базовые механизмы языка. Именно по причине такой гибкости открываются и возможности для злоупотреблений – но нас это уже не удивляет. С большими возможностями приходит большая ответственность.

Данная тема обычно не затрагивается в различных руководствах и вводных материалах по языку, поскольку считается «продвинутой» — но и с ней надо с чего-то начинать. Я немного поискал в онлайне и в качестве наилучшего введения в тему нашел соответствующий вопрос на StackOverflow и ответы на него [1].

Поехали. Все примеры кода приведены на Python 3.6 – на момент написания статьи это новейшая версия.

Первый контакт

Мы уже кое-что успели обсудить, но пока еще не видели, что представляет собой метакласс. Скоро разберемся с этим, но пока следите за моим рассказом. Начнем с чего-нибудь простого: создадим объект.

Мы также можем объявить и наш собственный класс:

Как видим, b и B во многих отношениях действуют похоже. Можно даже сделать выражение с вызовом функции, в котором использовались бы обе переменные, просто возвращены в данном случае будут разные вещи: b возвращает 5, как и указано в определении класса, тогда как B создает новый экземпляр класса.

Это сходство – не случайность, а намеренно спроектированная черта языка. В Python классы являются сущностями первой категории[5] (ведут себя как все нормальные объекты).

Более того, если классы – как объекты, то у них обязательно должен быть собственный тип:

Оказывается, никакого двойного дна здесь нет, поскольку type относится к собственному типу.

Рассмотрим это на практике:

Это наш первый метакласс. Мы могли бы сделать его определение еще более минималистичным, но хотели сделать, чтобы в итоге он делал хотя бы что-нибудь полезное.

Все-таки, метакласс сам по себе не так интересен. Интересное начинается, лишь когда мы создаем экземпляр метакласса. Давайте это и сделаем:

Сейчас можем посмотреть все типы наших переменных:

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

Полезный пример: синглтон

В этом разделе мы напишем совсем маленькую библиотеку, в которой будет малость метаклассов. Мы реализуем «эскиз» для паттерна проектирования синглтон [6] – это класс, который может иметь всего один экземпляр.

Честно говоря, его можно было бы реализовать и без всякого использования метаклассов, просто переопределив метод __new__ в базовом классе, так, чтобы он вернул ранее запомненный экземпляр:

Рассмотрим, каков он в действии:

Эту проблему можно решить, и не прибегая никоим образом к метаклассам, но решение с ними просто очевидное – так почему бы ими не воспользоваться?

Вот что получается:

Можем попробовать, а работает ли этот подход:

Поздравляем, по-видимому наша библиотека-синглтон работает именно так, как и планировалось!

На правах опытных проектировщиков библиотеки с метаклассами, давайте замахнемся на что-нибудь посложнее.

Полезный пример: упрощенное ORM

Как упоминалось выше, с паттерном синглтон можно красиво разобраться, слегка воспользовавшись метаклассами, но острой необходимости в них нет. Большинство реальных проектов, в которых метаклассы действительно используются – это те или иные вариации на тему ORM[7].

В качестве упражнения построим подобный пример, но сильно упрощенный. Это будет уровень сериализации/десериализации между классами Python и JSON.

Вот как должен выглядеть интерфейс, который мы хотим получить (смоделирован на Django ORM/SQLAlchemy):

Мы хотим иметь возможность определять классы и их поля вместе с типами. Для этого нам пригодилась бы возможность сериализовать наш класс в JSON:

И десериализовать его:

Напоминание о конструкторе типов

Вспомните эпизод из предыдущего раздела, когда мы определяли метод __init__ для нашего первого метакласса:

name – просто имя класса в формате строки

bases – кортеж базовых классов, может быть пустым

namespace – словарь всех полей, определенных внутри класса. Сюда идут все методы и переменные класса.

Вот и все, что здесь есть. На самом деле, можно было бы и не определять класс при помощи общего синтаксиса, а вызвать конструктор type напрямую:

Упрощенное ORM – грамотная программа

Мы уже знаем, чего хотим – написать библиотеку, удовлетворяющую требованиям указанного интерфейса. Мы также знаем, что будем решать эту задачу при помощи метаклассов.

Далее я приведу реализацию в стиле грамотного программирования. Код из этого раздела можно загрузить в интерпретатор Python и запустить.

Мы будем использовать всего один пакет – для синтаксического разбора/сериализации JSON:

Далее определим базовый класс для всех наших полей. Он устроен весьма просто, как и большинство других отдельных частей данной библиотеки. В нем есть реализация-заглушка для валидационной функции и пустое начальное значение.

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

Следующий фрагмент кода – это наш метакласс:

Единственная вещь, для которой нам нужен наш метакласс – чтобы он подключался в момент, когда создается наш класс, брал все определения полей и сохранял их все в одном месте.

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

У класса ORMBase три метода, и у каждого из них своя конкретная задача:

to_json – простейший из всех методов в классе. Просто принимает все значения полей и сериализует их в документ JSON.

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

Заключительные замечания

Весь код к этому посту можно скачать в репозитории на GitHub [8].

Надеюсь, эта статья вам понравилась и подсказала вам какие-то идеи. Метаклассы могут казаться немного непонятными и не всегда полезными. Однако, они определенно позволяют собирать элегантные библиотеки и интерфейсы, если уметь метаклассами пользоваться.

Подробнее о том, как метаклассы используются в реальной жизни, можно почитать в статье [9].

Источник

Использование метаклассов в 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

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% случаев вам вообще не нужно менять класс.

Источник


Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *