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

Метаклассы в 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 как «решения, которые ждут или ищут задачу».

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

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

Метаклассы – это классы, экземпляры которых являются классами. Давайте поговорим о специфике языка 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__. Также с помощью метаклассов можно:

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

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

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

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

Гуру Python Tim Peters

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

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

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

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

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

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

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

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

Источник

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

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

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

Но в Python классы — это так же еще и нечто большее. В Python все является объектами в том числе и классы. Как только вы используете ключевое слово class, Python выполняет его и создает OBJECT.

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

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

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

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

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

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

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

Но это не так динамично, так как вам все равно придется писать весь класс самостоятельно. Поскольку классы являются объектами, они должны быть чем-то порождены. Когда вы используете ключевое слово class, Python создает этот объект автоматически. Но, как и в большинстве случаев в Python, он позволяет делать это и вручную. Помните функции type? Старая добрая функция, которая позволяет узнать, к какому типу относится объект:

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

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

можно создать вручную следующим образом:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Что ж, я думаю, это вопрос согласованности с str, классом, который создает строковые объекты, и int с классом, который создает целочисленные объекты. type — это просто класс, который создает объекты class.

Вы можете убедиться в этом, проверив атрибут __class__.

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

Теперь, что такое __class__ любого __class__?

Итак, метакласс — это просто материал, который создает объекты class. Если хотите, можете назвать это «фабрикой классов». type — это встроенный метакласс, который использует Python, но, конечно, и вы можете создать свой собственный метакласс.

Атрибут __metaclass__

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

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

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

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

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

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

Если Python не может найти __metaclass__, он будет искать __metaclass__ на уровне MODULE и пытаться сделать то же самое (но только для классов, которые ничего не наследуют, в основном классов старого стиля).

Затем, если он вообще не может найти какой-либо __metaclass__, он будет использовать собственный метакласс Bar (первого родителя) (который может быть type по умолчанию) для создания объекта класса.

Теперь главный вопрос: что можно добавить в __metaclass__?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мы можем сделать его еще чище, используя super, который упростит наследование (потому что да, у вас могут быть метаклассы, унаследованые от метаклассов, унаследованые от type):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Еще хочу указать здесь ссылку на статью о вариантах использование метаклассов: Когда использовать метаклассы в Python: 5 интересных вариантов использования

Заключение

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

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

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

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

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

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

Источник

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

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