Метаклассы в Python
В этом руководстве мы расскажем, что такое метаклассы в Python, зачем они нужны и как их создавать.
Метакласс в Python — это класс классов, определяющий поведение класса. То есть класс сам по себе является экземпляром метакласса. Класс определяет поведение экземпляров этого класса. Чтобы хорошо понимать метаклассы, необходимо иметь предыдущий опыт работы с классами в Python. Поэтому, прежде чем углубиться в метаклассы, давайте рассмотрим несколько основных концепций.
От редакции Pythonist. О классах можно почитать в статье «Классы в Python».
В Python все является объектом
class TestClass(): pass my_test_class = TestClass() print(my_test_class) # Oytput: #
Классы в Python можно создавать динамически
Функция type() в Python позволяет нам определить тип объекта. Давайте проверим тип объекта, который мы только что создали:
type(TestClass) # type type(type) # type
Подождите, но что это было? Мы ожидали, что тип объекта, который мы создали выше, будет классом, однако это не так. Зафиксируйте, пожалуйста, эту мысль. Мы вернемся к ней чуть позже.
Кроме того, можно заметить, что type имеет тип type . Всё потому, что это экземпляр type .
Функция type , помимо определения типа объектов, имеет еще один волшебный вариант применения. С ее помощью можно динамически создавать классы. Давайте рассмотрим, как это делается.
Показанный ниже класс Pythonist будет создан с использованием type :
class Pythonist(): pass PythonistClass = type(‘Pythonist’, (), <>) print(PythonistClass) print(Pythonist()) # Output: # #
Здесь Pythonist — это имя класса, а Pythonist Class — это переменная, содержащая ссылку на класс.
При использовании type мы можем передавать атрибуты класса с помощью словаря:
PythonClass = type(‘PythonClass’, (), ) print(PythonClass.start_date, PythonClass.instructor) print(PythonClass) # Output: # August 2018 John Doe #
[python_ad_block]
Если мы хотим, чтобы наш PythonClass наследовался от класса Pythonist , мы передаем его нашему второму аргументу при определении класса с использованием type :
PythonClass = type(‘PythonClass’, (Pythonist,), ) print(PythonClass) # Output: #
Теперь, когда эти две концепции ясны, мы понимаем, что Python создает классы, используя метакласс. Мы видели, что все в Python является объектом и эти объекты создаются метаклассами.
Всякий раз, когда мы вызываем class для создания класса, есть метакласс, который колдует над созданием класса за кулисами. Мы уже видели, как это делает type . Это похоже на str , который создает строки, и int , который создает целые числа. В Python атрибут __class__ позволяет нам проверить тип текущего экземпляра. Давайте создадим строку и проверим ее тип.
article = 'metaclasses' article.__class__ # str
Мы также можем проверить тип, используя type(article) следующим образом:
type(article) # str
Проверив тип самого str , мы узнаем, что это тоже type :
type(str) # type
Если мы проверим тип float , int , list , tuple и dict с помощью type() , мы получим аналогичный результат. Это потому, что все эти объекты имеют тип type :
print(type(list),type(float), type(dict), type(tuple)) #
Итак, мы уже видели, как type создает классы. Следовательно, когда мы проверяем __class__ из __class__ , он должен возвращать type :
article.__class__.__class__ # type
Создание пользовательских метаклассов
В Python мы можем настроить процесс создания класса, передав ключевое слово metaclass в определение класса. Это также можно сделать, унаследовав класс, в который уже передали это ключевое слово. К примеру, это может выглядеть следующим образом:
class MyMeta(type): pass class MyClass(metaclass=MyMeta): pass class MySubclass(MyClass): pass
Ниже мы видим, что тип класса MyMeta — type , а тип MyClass и MySubClass — MyMeta .
print(type(MyMeta)) print(type(MyClass)) print(type(MySubclass)) # Output: # # #
При определении класса и отсутствии метакласса по умолчанию будет использоваться метакласс type . Если задан метакласс, не являющийся экземпляром type() , то он используется непосредственно как метакласс.
__new__ и __init__
Метаклассы также могут быть определены одним из двух способов, показанных ниже. Давайте рассмотрим разницу между ними:
class MetaOne(type): def __new__(cls, name, bases, dict): pass class MetaTwo(type): def __init__(self, name, bases, dict): pass
__new__ используется, когда нужно определить кортежи dict или base перед созданием класса. Возвращаемое значение __new__ обычно является экземпляром cls . __new__ позволяет подклассам неизменяемых типов настраивать создание экземпляров. Его можно переопределить в пользовательских метаклассах, чтобы настроить создание класса.
__init__ обычно вызывается после создания объекта для его инициализации.
Метод метакласса __call__
Согласно официальной документации, мы также можем переопределить другие методы класса. К примеру, определив собственный метод __call__() в метаклассе, что позволит настроить поведение при вызове класса.
Метод метакласса __prepare__
«После определения соответствующего метакласса подготавливается пространство имен класса.
Если у метакласса есть атрибут __prepare__ , он вызывается как namespace = metaclass.__prepare__(name, bases, **kwds) (где дополнительные аргументы ключевого слова, если они есть, берутся из определения класса).
Если метакласс не имеет __prepare__attribute , то пространство имен класса инициализируется как пустое упорядоченное отображение».
Проектирование Singleton с использованием метакласса
Данный шаблон проектирования ограничивает создание экземпляра класса только одним объектом. Это может оказаться полезным, например, при разработке класса для подключения к базе данных. Возможно, вы захотите иметь только один экземпляр класса соединения.
class SingletonMeta(type): _instances = <> def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(SingletonMeta,cls).__call__(*args, **kwargs) return cls._instances[cls] class SingletonClass(metaclass=SingletonMeta): pass
Заключение
В этой статье мы узнали о том, что такое метаклассы в Python и как мы можем реализовать их в нашем коде. Метаклассы могут применяться, среди прочего, для ведения журнала, регистрации классов во время создания и профилирования. Они кажутся довольно абстрактными понятиями, и у вас может возникнуть сомнение относительно целесообразности их использования. Лучше всего об этом сказал Тим Питерс, питонист с большим опытом:
«Метаклассы — это более глубокая магия, о которой 99% пользователей не должны беспокоиться. Если вы задаетесь вопросом, нужны ли они вам – значит, нет, вы в них не нуждаетесь (люди, которые действительно нуждаются в метаклассах, точно знают, что они им нужны, и не нуждаются в объяснении зачем)».
Надеемся, данная статья была вам полезна! Успехов в написании кода!
Определение метаклассов metaclass в Python
По умолчанию классы создаются с использованием класса type() . Тело класса выполняется в новом пространстве имен, а имя класса локально привязано к результату выполнения type(name, bases, namespace) .
Процесс создания класса можно настроить, передав ключевой аргумент metaclass в строке определения класса или наследовав от существующего класса, который включал такой аргумент. В следующем примере MyClass и MySubclass являются экземплярами Meta :
class Meta(type): pass class MyClass(metaclass=Meta): pass class MySubclass(MyClass): pass
Любые другие ключевые аргументы, указанные в определении класса, передаются во все операции metaclass , описанные ниже.
Когда выполняется определение класса, происходят следующие шаги:
- разрешаются записи MRO;
- определяется соответствующий метакласс;
- подготавливается пространство имен класса;
- выполняется тело класса;
- создается объект класса.
Разрешение записей MRO.
Если база, которая появляется в определении класса, не является экземпляром type , то по ней ищется метод __mro_entries__ . Если он найден, то он вызывается с исходным базовым кортежем. Этот метод должен возвращать кортеж классов, который будет использоваться вместо этой базы. Кортеж может быть пустым, в этом случае исходная база игнорируется.
Определение подходящего метакласса.
Соответствующий метакласс для определения класса определяется следующим образом:
- если не указаны ни базы, ни явный метакласс (как аргумент), то используется type() ;
- если указан явный метакласс и он не является экземпляром type() , то он используется непосредственно как метакласс;
- если экземпляр type() задан как явный метакласс или определены базы, то используется наиболее производный метакласс.
Наиболее производный метакласс выбирается из явно указанного метакласса (если есть) и метаклассов (то есть type(cls) ) всех указанных базовых классов. Самый производный метакласс — это тот, который является подтипом всех этих метаклассов-кандидатов. Если ни один из метаклассов-кандидатов не соответствует этому критерию, определение класса завершится ошибкой TypeError .
Подготовка пространства имен классов.
После того, как соответствующий метакласс идентифицирован, подготавливается пространство имен класса. Если у метакласса есть атрибут __prepare__ , то он именуется как namespace=metaclass.__prepare__(name, base, ** kwds) , где дополнительные ключевые аргументы, если таковые имеются, берутся из определения класса. Метод __prepare__ должен быть реализован как метод класса classmethod() . Пространство имен, возвращаемое __prepare__ , передается в __new__ , но когда создается последний объект класса, пространство имен копируется в новый словарь dict .
Если метакласс не имеет атрибута __prepare__ , то тогда пространство имен класса инициализируется как пустое упорядоченное отображение (словарь).
Выполнение тела класса.
Тело класса выполняется (приблизительно) как exec(body, globals(), namespace) . Ключевое отличие от обычного вызова функции exec() состоит в том, что лексическая область видимости позволяет телу класса (включая любые методы) ссылаться на имена из текущей и внешней областей, когда определение класса происходит внутри функции.
Однако, даже когда определение класса происходит внутри функции, методы, определенные внутри класса, по-прежнему не могут видеть имена, определенные в области класса. Доступ к переменным класса должен осуществляться через первый параметр методов экземпляра или класса или через неявную ссылку __class__ с лексической областью видимости, описанную в «Создании объекта класса».
Создание объекта класса.
Как только пространство имен класса заполнено путем выполнения тела класса, объект класса создается путем вызова metaclass(name, bases, namespace, **kwds) . Дополнительные ключевые слова **kwds , переданные здесь, такие же, как те, которые переданы в __prepare__ .
Этот объект класса является тем, на который будет ссылаться форма с нулевым аргументом super().__class__ — это неявная ссылка на замыкание, созданная компилятором, если какие-либо методы в теле класса ссылаются либо на __class__ , либо на super() . Это позволяет нулевой форме аргумента функции super() правильно идентифицировать определяемый класс на основе лексической области видимости, в то время как класс или экземпляр, который был использован для выполнения текущего вызова, идентифицируется на основе первого аргумента, переданного методу.
Подробности реализации CPython: в CPython 3.6 и более поздних версиях ячейка __class__ передается метаклассу как запись __classcell__ в пространстве имен классов. Если он присутствует, то он должен быть распространен до type.__new__ , чтобы класс был правильно инициализирован. Невыполнение этого требования приведет к ошибке RuntimeError в Python 3.8.
При использовании метакласса type по умолчанию или любого метакласса, который в конечном итоге вызывает type.__ new__ после создания объекта класса, вызываются следующие дополнительные шаги настройки:
- сначала type.__new__ собирает все дескрипторы в пространстве имен класса, которые определяют метод __set_name__() ;
- во-вторых, все эти методы __set_name__ вызываются с определяемым классом и присвоенным именем этого конкретного дескриптора;
- наконец, вызывается хук __init_subclass__() для непосредственного родителя нового класса в порядке разрешения его методов.
После того, как объект класса создан, он передается декораторам класса, включенным в определение класса (если есть) и полученный объект привязывается в локальном пространстве имен как определенный класс.
Когда новый класс создается по type.__new__ , то объект, указанный в качестве параметра пространства имен, копируется в новое упорядоченное сопоставление (словарь), а исходный объект отбрасывается. Новая копия упаковывается в прокси только для чтения, который становится атрибутом __dict__ объекта класса.
Где используют метаклассы?
Метакласс чаще всего используется в качестве фабрики классов, а вообще возможности использования метаклассов безграничны.
Некоторые из идей включают:
- перечисление,
- ведение журнала,
- проверку интерфейса,
- автоматическое делегирование,
- автоматическое создание свойств,
- прокси,
- фреймворки,
- автоматическую блокировку/синхронизацию ресурсов.
Если не требуется сложные изменения класса, метаклассы использовать не стоит. Просто изменить класс можно двумя способами:
- Руками
- Декораторами класса
В 99% случаев лучше использовать эти методы, а в 98% изменения класса вообще не нужны.
Причина сложности кода, использующего метаклассы, заключается не в самих метаклассах. Код сложный потому, что обычно метаклассы используются для сложных задач, основанных на наследовании, интроспекции и манипуляции такими переменными, как __dict__ .
Пример пользовательского метакласса.
Основной целью метакласса является автоматическое изменение класса во время его создания. Обычно это делается для API, когда нужно создать классы, соответствующие текущему контексту. Например необходимо, что бы все классы в модуле имели свои атрибуты в верхнем регистре. Чтобы добиться такого поведения, определим метакласс и передадим его переменной __metaclass__ на уровне модуля или можно вручную передавать его в класс в качестве ключевого аргумента metaclass .
class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = <> for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # повторно используем метод type.__new__, # это базовое ООП, в нем нет ничего волшебного return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr) class Foo(metaclass=UpperAttrMetaclass): bar = 'bip' print(hasattr(Foo, 'bar')) # False print(hasattr(Foo, 'BAR')) # True f = Foo() print(f.BAR) # 'bip'
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Пространство имен и область видимости в классах
- Определение классов
- Объект класса и конструктор класса
- Создание экземпляра класса
- Метод экземпляра класса
- Что такое метод класса и зачем нужен
- Что такое статический метод в классах Python и зачем нужен
- Атрибуты класса и переменные экземпляра класса
- Кэширование методов экземпляра декоратором lru_cache
- Закрытые/приватные методы и переменные класса Python
- Наследование классов
- Множественное наследование классов
- Абстрактные классы
- Перегрузка методов в классе Python
- Что такое миксины и как их использовать
- Класс Python как структура данных, подобная языку C
- Создание пользовательских типов данных
- Специальные (магические) методы класса Python
- Базовая настройка классов Python магическими методами
- Настройка доступа к атрибутам класса Python
- Дескриптор класса для чайников
- Протокол дескриптора класса
- Практический пример дескриптора
- Использование метода .__new__() в классах Python
- Специальный атрибут __slots__ класса Python
- Специальный метод __init_subclass__ класса Python
- Определение метаклассов metaclass
- Эмуляция контейнерных типов в классах Python
- Другие специальные методы класса
- Как Python ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Метаклассы — Python: Погружаясь в классы
Python — язык программирования, в котором все является объектом, включая классы и функции. Но иногда стандартных возможностей классов недостаточно, и возникает необходимость более тонкого управления поведением классов. Здесь-то и появляется проблема: как добавить дополнительную функциональность или контроль над процессом создания и поведением классов?
Когда мы говорим об объектах в Python, мы часто думаем о простых типах данных или о сложных структурах, таких как классы. Однако в Python даже классы являются объектами, и эти объекты-классы создаются с использованием метаклассов, в основном метакласса type .
В языке Python решением этой проблемы являются метаклассы. Они позволяют воздействовать на процесс создания и поведение классов.
В этом уроке мы рассмотрим, что такое метаклассы, как их создать и применять для решения задач, связанных с управлением классами, а также возможные сценарии их использования.
Что такое метаклассы
Метакласс — это «класс классов». Как класс определяет поведение объекта, метакласс определяет поведение класса. По умолчанию в Python все классы создаются из встроенного метакласса type .
Самый простой способ использования метаклассов — это их непосредственное использование при создании классов. Мы можем напрямую использовать type как метакласс для создания классов:
MyClass = type('MyClass', (), <>)
Это создаст новый класс MyClass без родителей кроме базового класса object и без методов или атрибутов.
В этом примере мы используем встроенный метакласс type для создания нового класса MyClass . Первый аргумент type — это строка с именем класса, второй — кортеж с родительскими классами. В нашем случае он пуст, что означает, что у класса нет родителей кроме базового класса object . А третий — словарь с атрибутами и методами класса. Он тоже пустой.
Но что если мы хотим добавить методы или атрибуты в класс? Мы можем сделать это, передав их в виде словаря третьим аргументом в type :
def my_method(self): print("Hello, World!") MyClass = type('MyClass', (), 'my_method': my_method>) obj = MyClass() obj.my_method() # Hello, World!
Здесь мы добавляем метод my_method в класс MyClass . Мы определяем функцию my_method и затем включаем ее в словарь атрибутов и методов класса, который передаем в type как третий аргумент.
Но что, если нам нужно добавить специфическое поведение при создании класса? Рассмотрим, как создавать собственные метаклассы.
Как создать собственные метаклассы
Мы можем создать собственные метаклассы, наследуясь от type . Это позволяет переопределить методы метакласса:
class MyMeta(type): def __init__(cls, name, bases, attrs): print(f"Создание класса name>") super().__init__(name, bases, attrs) class MyClass(metaclass=MyMeta): pass # Создание класса MyClass
В этом примере мы создаем собственный метакласс MyMeta , который выводит сообщение при создании класса. Метод __init__ в метаклассе принимает четыре аргумента:
- cls — это новый класс, который создается
- name — имя класса
- bases — родительские классы
- attrs — словарь с атрибутами и методами класса
Мы переопределяем __init__ для добавления собственного поведения, но также вызываем __init__ родительского класса, чтобы убедиться, что класс создается правильно.
Затем создаем класс MyClass с MyMeta в качестве его метакласса. Когда мы делаем это, наш метакласс автоматически вызывается для создания MyClass , и мы видим сообщение «Создание класса MyClass».
Создание собственных метаклассов открывает множество возможностей. Теперь поговорим о том, когда и как их следует применять.
Как применять метаклассы
Метаклассы используются редко из-за их сложности, но они могут быть чрезвычайно мощными. Один из наиболее известных примеров использования метаклассов — это фреймворк Django. Django использует метаклассы для создания моделей базы данных. Это позволяет разработчикам определять структуры своих баз данных с помощью простого Python-кода, и метаклассы автоматически преобразуют эти определения в SQL-запросы для создания таблиц.
Для создания модели в Django, разработчики определяют классы, которые наследуются от django.db.models.Model . Но что интересно, когда вы определяете поля модели, например, CharField или IntegerField , вы на самом деле не создаете экземпляры этих полей. Вместо этого, Django использует метаклассы для преобразования этих определений полей в столбцы базы данных:
from django.db import models class Person(models.Model): name = models.CharField(max_length=100) age = models.IntegerField()
Здесь Person — это модель, которая будет преобразована в таблицу базы данных с полями name и age .
Когда вы определяете такой класс, Django автоматически использует метакласс, который проходит по вашим определениям полей и создает соответствующие столбцы в базе данных. Так, CharField будет преобразован в столбец типа VARCHAR в базе данных, а IntegerField — в столбец типа INT.
Давайте рассмотрим как это работает. Модель Model Django имеет метакласс под названием ModelBase . Когда вы определяете класс, такой как Person , ModelBase автоматически анализирует все его атрибуты. Он определяет, какие из этих атрибутов являются определениями полей, и затем создает структуру для таблицы базы данных.
Метакласс ModelBase также обеспечивает дополнительные функции, такие как создание методов для запросов к базе данных и работу с наследованием моделей.
Таким образом в Django разработчики могут определить структуры данных с помощью понятного Python-кода, и Django автоматически преобразует это в SQL и обеспечивает интерфейс для работы с данными.
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Что такое метаклассы в Python
Погрузитесь в мир метаклассов Python и узнайте, как они контролируют создание и изменение классов для метапрограммирования.
Алексей Кодов
Автор статьи
10 июля 2023 в 17:48
Метаклассы являются основным инструментом метапрограммирования в Python. В этой статье мы разберемся, что такое метаклассы, как они работают и для чего они используются.
Определение метакласса
Метакласс — это класс, который определяет поведение и свойства других классов, называемых подклассами. В Python, класс type является метаклассом по умолчанию для всех классов. Метаклассы позволяют вам контролировать создание, изменение и удаление классов и их атрибутов.
Создание метакласса
Чтобы создать метакласс, нужно унаследовать его от базового метакласса type и переопределить один или несколько его методов, таких как __new__ , __init__ и __call__ . Вот простой пример метакласса:
class MyMeta(type): def __new__(cls, name, bases, dct): print(f"Creating a new class named ") return super().__new__(cls, name, bases, dct)
Теперь вы можете использовать этот метакласс для создания новых классов:
class MyClass(metaclass=MyMeta): pass
При создании класса MyClass , метакласс MyMeta будет вызван, и вы увидите сообщение о создании нового класса.
Python-разработчик: новая работа через 9 месяцев
Получится, даже если у вас нет опыта в IT

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