В этом руководстве мы расскажем, что такое метаклассы в Python, зачем они нужны и как их создавать.
Метакласс в Python — это класс классов, определяющий поведение класса. То есть класс сам по себе является экземпляром метакласса. Класс определяет поведение экземпляров этого класса. Чтобы хорошо понимать метаклассы, необходимо иметь предыдущий опыт работы с классами в Python. Поэтому, прежде чем углубиться в метаклассы, давайте рассмотрим несколько основных концепций.
От редакции Pythonist. О классах можно почитать в статье «Классы в Python».
class TestClass(): pass my_test_class = TestClass() print(my_test_class) # Oytput: # <__main__.TestClass object at 0x7f6fcc6bf908>
Функция 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: # <class '__main__.DataCamp'> # <__main__.DataCamp object at 0x7f6fcc66e358>
Здесь Pythonist
— это имя класса, а Pythonist Class
— это переменная, содержащая ссылку на класс.
При использовании type
мы можем передавать атрибуты класса с помощью словаря:
PythonClass = type('PythonClass', (), {'start_date': 'August 2018', 'instructor': 'John Doe'} ) print(PythonClass.start_date, PythonClass.instructor) print(PythonClass) # Output: # August 2018 John Doe # <class '__main__.PythonClass'>[python_ad_block]
Если мы хотим, чтобы наш PythonClass
наследовался от класса Pythonist
, мы передаем его нашему второму аргументу при определении класса с использованием type
:
PythonClass = type('PythonClass', (Pythonist,), {'start_date': 'August 2018', 'instructor': 'John Doe'} ) print(PythonClass) # Output: # <class '__main__.PythonClass'>
Теперь, когда эти две концепции ясны, мы понимаем, что 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)) # <class 'type'> <class 'type'> <class 'type'> <class 'type'>
Итак, мы уже видели, как 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: # <class 'type'> # <class '__main__.MyMeta'> # <class '__main__.MyMeta'>
При определении класса и отсутствии метакласса по умолчанию будет использоваться метакласс type
. Если задан метакласс, не являющийся экземпляром type()
, то он используется непосредственно как метакласс.
Метаклассы также могут быть определены одним из двух способов, показанных ниже. Давайте рассмотрим разницу между ними:
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__()
в метаклассе, что позволит настроить поведение при вызове класса.
Согласно документам модели данных Python:
«После определения соответствующего метакласса подготавливается пространство имен класса.
Если у метакласса есть атрибут __prepare__
, он вызывается как namespace = metaclass.__prepare__(name, bases, **kwds)
(где дополнительные аргументы ключевого слова, если они есть, берутся из определения класса).
Если метакласс не имеет __prepare__attribute
, то пространство имен класса инициализируется как пустое упорядоченное отображение».
Данный шаблон проектирования ограничивает создание экземпляра класса только одним объектом. Это может оказаться полезным, например, при разработке класса для подключения к базе данных. Возможно, вы захотите иметь только один экземпляр класса соединения.
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% пользователей не должны беспокоиться. Если вы задаетесь вопросом, нужны ли они вам – значит, нет, вы в них не нуждаетесь (люди, которые действительно нуждаются в метаклассах, точно знают, что они им нужны, и не нуждаются в объяснении зачем)».
Надеемся, данная статья была вам полезна! Успехов в написании кода!
Перевод статьи «Introduction to Python Metaclasses».
Управление памятью - важный, но часто упускаемый из виду аспект программирования. При неправильном подходе оно…
Как возникает круговой импорт? Эта ошибка импорта обычно возникает, когда два или более модуля, зависящих…
Вы когда-нибудь оказывались в ситуации, когда скрипт на Python выполняется очень долго и вы задаетесь…
В этом руководстве мы разберем все, что нужно знать о символах перехода на новую строку…
Блок if __name__ == "__main__" в Python позволяет определить код, который будет выполняться только при…
Давайте разберем, как настроить модульные тесты для экземпляров классов. Мы напишем тесты для проверки функциональности…