Курс по Python ООП: https://stepik.org/a/116336
На
этом занятии мы затронем тему метаклассов. Давайте вначале разберемся, что
стоит за этим умным словом. Как я уже не раз говорил на наших занятиях, булевы
значения, строки, числа, списки, словари и т.п. все это объекты в языке Python. И эти объекты образованы от соответствующих классов (типов
данных): bool, str, int, float, list, dict,
функции и т.д. Но эти классы также являются и объектами, потому что все в Python – это объекты, даже классы.
Да, классы – это объекты, которые позволяют создавать другие объекты с
конкретным содержимым. А раз классы – это объекты, то должно быть нечто, что
создает и их. И это нечто в Python
называется метаклассом. Причем, метакласс – это тоже объект (в Python все объекты). Но это объект
особого рода, который нельзя динамически порождать каким-нибудь другим мета-метаклассом.
Он является вершиной, отправной точкой для создания обычных классов и, как
следствие, их объектов.
Но
что является метаклассом в языке Python? Наверное,
вас немного удивит, но это давно знакомый вам объект type,
который мы использовали для определения типов объектов. Но это, если он
вызывается с одним аргументом. Если ему передать три аргумента:
type(<имя класса>, <кортеж родительских классов>,
<словарь с атрибутами и их значениями>)
то
данный объект начинает работать совершенно по-другому, а именно, создает
(динамически) новый класс, новый тип данных в программе.
В
действительности, все стандартные типы данных, функции и обычные классы – порождены
метаклассом type. В этом легко убедиться, если вызвать функцию type (я ее так называю для удобства) от встроенных типов данных:
или классов:
Всюду
увидим
<class 'type'>
Это,
как раз и говорит, что все эти объекты сформированы метаклассом type.
Наверное,
на этом этапе у вас в голове крутится вопрос: зачем все это надо? Мы же и так
можем объявлять классы. Для чего их создавать динамически в процессе работы
программы? На этот вопрос хорошо ответил гуру Питона Тим Питерс. Он сказал:
Метаклассы
– это глубокая магия, о которой 99% пользователей даже не нужно задумываться.
Если вы думаете, нужно ли вам их использовать — вам не нужно (люди, которым они
реально нужны, точно знают, зачем они им, и не нуждаются в объяснениях,
почему).
Но,
на мой взгляд, чтобы лучше понимать, как функционирует язык Python, все же лучше ознакомиться с этой темой. И, кроме того, кто
знает, возможно в будущем именно вам это и пригодится.
Итак,
давайте теперь попробуем создать свой класс с помощью метакласса type. Для простоты сделаем определение класса точки на плоскости:
class Point:
MAX_COORD = 100
MIN_COORD = 0
Через
объект type это будет выглядеть так:
A = type('Point', (), {'MAX_COORD': 100, 'MIN_COORD': 0})
Здесь
переменная A – ссылка на новый созданный класс с именем Point. Да, так как классы – это тоже объекты, то мы можем на них
ссылаться через разные переменные. И, далее, можно создать экземпляр этого
класса, используя переменную A:
Вот
мы с вами только что динамически в программе сформировали новый класс через
метакласс type и увидели, что новый класс корректно работает – создает свои
экземпляры.
При
необходимости, можно дополнительно прописывать базовые классы, передавая их
список вторым аргументом метаклассу type.
Например, пусть имеются два класса:
class B1: pass
class B2: pass
И,
далее, мы можем их указать в качестве базовых при создании нового класса:
A = type('Point', (B1, B2), {'MAX_COORD': 100, 'MIN_COORD': 0})
Если
теперь вывести коллекцию __mro__
класса A:
то
увидим всю цепочку наследования:
(<class '__main__.Point'>, <class '__main__.B1'>,
<class '__main__.B2'>, <class 'object'>)
Также,
при динамическом создании новых классов, в них можно определять и методы.
Сделать это можно, по крайней мере, двумя способами. В первом использовать уже
существующую функцию, например:
def method1(self):
print(self.__dict__)
и
указать ссылку на нее при создании атрибутов класса:
A = type('Point', (), {'method1': method1})
Создадим
экземпляр класса Point и вызовем этот метод:
Как
видите, все работает. Во втором способе мы можем определить метод
непосредственно в словаре через лямбда-функцию:
A = type('Point', (), {'MAX_COORD': 100, 'method1': lambda self: self.MAX_COORD})
pt = A()
pt.method1()
Но
так можно определять только простейшие методы. Функции со сложным поведением
придется задавать отдельно, а затем, добавлять в качестве методов в создаваемый
класс.
Я,
думаю, вы теперь представляете, что такое метакласс и как можно динамически
создавать новые классы в программе с помощью объекта type. Однако,
описывать сложный алгоритм формирования новых классов непосредственно через
объект type не очень удобно. Мы видели, что даже добавление новых методов
происходит несколько непривычно и как-то «коряво». Благо, выход есть. В Python мы можем конструировать свои собственные метаклассы, которые,
конечно, явно или неявно образуются от объекта type. Но
об этом мы поговорим уже на следующем занятии.
Курс по Python ООП: https://stepik.org/a/116336