Метаклассы в API ORM Django

Курс по Python ООП: https://stepik.org/a/116336

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

Фреймворк Django использует метаклассы для связи объектов с записями БД. То есть, метаклассы используются для реализации его API для ORM. В частности, он делает следующее. Можно определить класс модели с набором атрибутов, которые будут представлять соответствующие поля таблицы БД. Например, предположим, что имеется класс Women для работы с таблицей, содержащей поля title, content, photo и т.д.:

class Women(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField(blank=True)
    photo = models.ImageField(upload_to="photos/%Y/%m/%d/")
    time_create = models.DateTimeField(auto_now_add=True)
    time_update = models.DateTimeField(auto_now=True)
    is_published = models.BooleanField(default=True)

Затем, во фреймворке Django нам достаточно создать экземпляр этого класса:

w = Women(title='Энн Хэтэуэй')

чтобы прочитать все записи содержащие поле title равное 'Энн Хэтэуэй'. Причем сами данные появляются автоматически в локальных свойствах объекта w. То есть, далее мы можем обратиться к данным из таблицы, например, так:

w.photo
w.content

И так далее. Вот эта магия по созданию объекта, в котором сразу оказываются нужные данные с соответствующими наборами локальных свойств и выполняется на уровне метаклассов. В действительности, базовый класс Model, как раз и реализует этот метакласс, добавляя необходимый функционал в класс модели Women.

Это конкретный пример, где используются метаклассы. Чтобы все было более понятно, давайте сделаем метакласс, который бы позволял при создании объектов класса автоматически добавлять в него локальные свойства с теми же именами, что и атрибуты класса. То есть, мы повторим некоторый функционал API ORM Django.

Вначале определим метакласс, в котором пропишем методы create_local_attrs и __init__:

class Meta(type):
    def create_local_attrs(self, *args, **kwargs):
        for key, value in self.class_attrs.items():
            self.__dict__[key] = value
 
    def __init__(cls, name_class, base, attrs):
        cls.class_attrs = attrs
        cls.__init__ = Meta.create_local_attrs

Первый метод create_local_attrs будет в дальнейшем являться инициализатором создаваемого класса. И в этом инициализаторе мы, как раз и создаем локальные свойства в экземплярах класса. Для этого в самом классе мы создаем специальный словарь class_attrs, который содержит все атрибуты класса с их значениями. Фактически, класс Women у нас теперь становится таким:

class Women:
    class_attrs = {'title': 'заголовок', 'content': 'контент', 'photo': 'путь к фото'}
    title = 'заголовок'
    content = 'контент'
    photo = 'путь к фото'
 
    def __init__(self, *args, **kwargs):
        for key, value in self.class_attrs.items():
            self.__dict__[key] = value

Конечно, метаклассы во фреймворке Django гораздо изощреннее нашего с вами примера. Я здесь лишь хотел показать саму идею их использования при создании различных API. А, вообще, конечно следует избегать их применения на практике, так как они несколько запутывают программный код и являются источником трудно отслеживаемых ошибок. Здесь всегда следует держать в голове высказывание гуру Питона Тима Питерса:

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

Надеюсь, они вам не понадобятся, а если понадобятся, то вы теперь имеете представление, что это такое.

Курс по Python ООП: https://stepik.org/a/116336

Видео по теме