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

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

Метаклассы – это такие классы, экземпляры которых сами являются классами. Подобно тому, как «обычный» класс определяет поведение экземпляров класса, метакласс определяет и поведение классов, и поведение их экземпляров.

Метаклассы поддерживаются не всеми объектно-ориентированными языками программирования. Те языки программирования, которые их поддерживают, значительно отличаются по способу их реализации. Но в Python метаклассы есть.

Некоторые программисты рассматривают метаклассы в Python как «решения, которые ждут или ищут задачу».

У метаклассов множество применений. Выделим несколько из них:

Логирование и профилирование;

Регистрация классов во время создания;

Автоматическое создание свойств;

Автоматическая блокировка/синхронизация ресурсов.

Определение метаклассов

Давайте создадим совсем простой метакласс. Он ничего не умеет, кроме вывода содержимого своих аргументов в методе _new_ и возврата результата вызова type._new_ :

А теперь используем метакласс LittleMeta в следующем примере:

В главе «Type and Class Relationship» мы выяснили, что после обработки определения класса Python вызывает:

Но не в том случае, когда метакласс был объявлен в заголовке. Именно так мы и сделали в нашем прошлом примере. Наши классы Philosopher1, Philosopher2 и Philosopher3 были «прицеплены» к метаклассу EssentialAnswers. И вот почему EssentialAnswers будет вызван вместо type:

Если быть точным, то аргументам вызовов будет даны следующие значения:

Другие классы Philosopher будут вести себя аналогично.

Создаем синглтоны с помощью метаклассов

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

Источник

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Создание

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

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

Источник

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

Как сказал один из пользователей 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% случаев вам вообще не нужно изменять классы 🙂

Источник

Что такое метаклассы в Python?

Как обычно проходит собеседования на позицию разработчика Python? Обычно одним из первых вопросов будет просьба рассказать о типах данных (или составных типах данных) в Python. Потом через несколько других общих вопросов разговор обязательно перейдет к теме дескрипторов и метаклассов в Python. И хотя это такие вещи, которые в реальной практике редко когда приходится использовать, каждый разработчик должен иметь хотя бы общее представление о них, — пишет сайт webdevblog.ru.

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

Давайте сейчас создадим в памяти объект с именем «ObjectCreator».

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

Но все же это объект, а значит:

В общем в Python вы можете делать с классом все, что можно делать с объектом. В том числе создавать класс на лету.

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

Поскольку классы являются объектами, вы можете создавать их на лету, как и любой объект.

Во-первых, вы можете создать класс в функции, используя ключевое слово class :

Что ж, у type есть и совершенно другие способности, он также может создавать классы на лету. type может принимать описание класса как параметры и возвращать класс. (Я знаю, это звучит глупо, что одна и та же функция может иметь два совершенно разных использования в зависимости от параметров, которые вы ей передаете. Это проблема из-за обратной совместимости в Python).

Итак, type работает следующим образом:

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

Вы заметите, что мы используем «MyShinyClass» как имя класса и как переменную для хранения ссылки на класс. Они могут быть разными, но нет причин усложнять ситуацию. type принимает словарь для определения атрибутов класса.

Может быть переведен на:

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

И, конечно, вы можете унаследоваться от него. То есть такое:

можно переделать в такое:

В конце концов, вы захотите добавить методы в свой класс. Просто определите функцию с соответствующей подписью и назначьте ее как атрибут.

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

Что такое метаклассы

Метаклассы — это «материал», который создает классы.

Вы определяете классы для создания объектов, верно? Но мы узнали, что классы Python — это объекты. Что ж, метаклассы создают эти объекты. Это классы классов, и их можно изобразить так:

Мы уже обсудили, что этот type позволяет делать что-то вроде этого:

Это потому, что функции type на самом деле является метаклассом. type — это метакласс, который Python использует для создания всех классов за кулисами.

Теперь вы можете спросить, почему type пишется с маленькой буквы?

Все является объектом в Python. Сюда входят целые числа, строки, функции и классы. Все они объекты. И все они созданы из класса:

Атрибут __metaclass__

В Python 2 вы можете добавить атрибут __metaclass__ при описании класса:

Когда вы делаете это:

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

Ответ — то, что может создать класс.

А что можно создать класс? type или что-либо, что его подклассы используют для этого.

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

В Python 3 был изменен синтаксис для установки метакласса:

то есть атрибут __metaclass__ больше не используется в качестве аргумента ключевого слова в списке базовых классов.

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

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

Основная цель метакласса — автоматически изменять класс при его создании.

Обычно вы делаете это для API, когда хотите создать классы, соответствующие текущему контексту.

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

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

К счастью, __metaclass__ на самом деле может быть любым вызываемым, он не обязательно должен быть формальным классом (то есть что-то с ‘class’ в имени не обязательно должно быть классом).

Итак, мы начнем с простого примера, используя функцию.

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

Давайте перепишем приведенное выше, но с более короткими и более реалистичными именами переменных, теперь, когда мы знаем, что они означают:

О, и в python 3, если вы выполните этот вызов с аргументами, например:

Это переводится в метаклассе для его использования:

Ну собственно вот и все. Больше нечего сказать о метаклассах.

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

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

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

Поскольку __metaclass__ может принимать любые вызываемые объекты, зачем использовать класс, если он явно более сложен?

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

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

А теперь большой вопрос. Зачем вам использовать какую-то непонятную функцию, подверженную ошибкам?

Ну, обычно вам это не нужно:

Метаклассы — это более глубокая магия, и 99% пользователей не должны использовать ее. Если вы задаетесь вопросом, нужны ли это вам, то скорее всего это вам не нужно (люди, которым это действительно нужно, точно знают, что это такое, и не нуждаются в объяснении почему).

Основной вариант использования метакласса — создание API. Типичным примером этого является Django ORM. Это позволяет вам определить что-то вроде

Но если вы используете это так:

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

Заключение

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

В Python все является объектом, и все они являются экземплярами классов или экземплярами метаклассов.

type на самом деле является отдельным метаклассом. И это не то, что можно было бы воспроизвести на чистом Python, это делается путем небольшого мошенничества на уровне реализации.

Во-вторых, метаклассы сложны. Возможно, вы не стоит их использовать для очень простых изменений класса. Лучше изменять классы, используя два других метода:

В 99% случаев, когда вам нужно изменить класс, вам лучше использовать их.

Но в 98% случаев вам вообще не нужно менять класс.

Источник

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

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