Как стать автором
Обновить

Комментарии 10

Классное объяснение, спасибо!

Ничего классного. Не рассказано главное - зачем?

Это все здорово. Но это можно найти в любой книжке более-менее приличной по питону. А практическая польза? Вы можете привести хоть один кейс практического использования метаклассов?

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

добавил

"Ничего ты не знаешь, Джон Сноу"

Если рассматривать статью, как попытку разобраться в метаклассах. То зачет. Попытался.

Если рассматривать статью, как попытку разобраться в метаклассах. То мимо. Не разобрался.

Просто и понятно написано тут https://docs.python.org/3/reference/datamodel.html#metaclasses, но обычно это никто не читает.

  1. Объекты класса как-то себя "проявляют" через методы и содержат атрибуты. Получить класс объекта можно командой type(obj).

  2. Классы задают базовое поведение и задают шаблоны атрибутов для объектов класса. Любой класс это синглтон-объект, который как-то себя "проявляет" через методы и содержит атрибуты. Получить метакласс класса можно командой type(cls). Именно потому, что класс это тоже объект.

  3. Метаклассы задают базовое поведение и задают шаблоны атрибутов для Классов, как объектов. Любой метакласс это синглтон-объект, который как-то себя "проявляет" через методы и содержит атрибуты. Получить Метакласс метакласса можно командой type(metacls). Именно потому, что метакласс - это тоже объект.

  4. Можно продолжить. Создав мета мета класс, который будет задавать базовое поведение и задают шаблоны атрибутов для Мета классов, как объектов.

Фактически у нас есть только одно взаимоотношение. Класс --> Объект.

В списке выше это 2 --> 1, 3 --> 2, 4 --> 3, ...

Метакласс MyMetaв примере статьи, например, управляет поведением инициализации объекта классом MyClass.

Первый пример применения метакласса: фактически "Мета классом" мы "донастраиваем" работу некоторых методов класса. В Django метакласc модели превращает атрибуты-поля Класса Модели в атрибуты-дескрипторы объекта на моменте инициализации классом объекта. Если бы этого не происходило, то в объекте мы бы имели атрибут-поле (models.RelatedField). Но в объекте мы имеем models.DeferredDescriptor.

Метакласс имеет не так много методов, поскольку не так уж и много нам надо делать на этом уровне. Базовый метакласс, это который type, например, при создании любого класса добавляет ему набор стандартных дандер-методов, типа __getattr__, и т.п. В твоем примере ты создал class MyClass() ничего ему не объявил, а у класса есть метод __str__. Спасибо метаклассу type который по умолчанию является конструктором класса и делает всю грязную работу за нас.

Это может пригодиться, если хочется поменять некоторые атодобавляемые методы у ВСЕХ классов этого метакласса. Опять Django. Добавляемый метод __eq__ у класса модели переопределен. Теперь в классе это поведение поменяно. И объекты этого класса в момент сравнения начинают меряться obj.pk ? obj2.pk вместо стандартного поведения id(obj) ? id(obj2)

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

Поскольку Метакласс позволяет переопределять дандер и не только методы на момент объявления класса (это же возможно сделать и миксином и наследованием и декоратором) то можно переопределить "__div__" и всю остальную математику и получить класс Path из Pathlib который реализует DSL, в данном случае это привычный file system syntax. Но важно, что этот класс на самом деле - это несколько классов PosixPath, WindowsPath etc. Без метакласса надо было бы переопределять математику в каждом из них ручками. Количество кода увеличилось бы. А через метакласс удалось соблюсти DRY да и KISS тоже где-то рядом.

По сути метакласс - это всегда синтаксический сахар сильно уменьшающий количество кода, который надо писать. Тут нет магии или мистики. Это просто. Но не для всех.

Классы задают базовое поведение и задают шаблоны атрибутов для объектов класса. Любой класс это синглтон-объект, который как-то себя "проявляет" через методы и содержит атрибуты. Получить метакласс класса можно командой type(cls). Именно потому, что класс это тоже объект.

Откуда это взялось? Или я_точно_синглетон = dict() тоже синглетон? Класс это экземпляр класса type или одного из его наследников, не более.

Метакласс имеет не так много методов, поскольку не так уж и много нам надо делать на этом уровне. Базовый метакласс, это который type, например, при создании любого класса добавляет ему набор стандартных дандер-методов, типа getattr, и т.п. В твоем примере ты создал class MyClass() ничего ему не объявил, а у класса есть метод str. Спасибо метаклассу type который по умолчанию является конструктором класса и делает всю грязную работу за нас.

type ничего не добавляет своим наследникам. Единственное что type.new делает, так это создаёт новый экземпляр переданного класса и заполняет его словарь, используя переданные параметры. Ну и валидацию выполняет, чтобы от некоторых типов не наследовались, но это не суть важно.

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

Это плохой пример использования метаклассов. Как вы уже и написали, миксины для этого подойдут гораздо лучше. Конечно, не стоит специально избегать создание метаклассов, но и городить их почём зря, это тоже не лучшая практика.
Действительно хорошим примером метакласса может послужить Enum из стандартной библиотеки.

Поскольку Метакласс позволяет переопределять дандер и не только методы на момент объявления класса (это же возможно сделать и миксином и наследованием и декоратором) то можно переопределить "div" и всю остальную математику и получить класс Path из Pathlib который реализует DSL, в данном случае это привычный file system syntax. Но важно, что этот класс на самом деле - это несколько классов PosixPath, WindowsPath etc. Без метакласса надо было бы переопределять математику в каждом из них ручками. Количество кода увеличилось бы. А через метакласс удалось соблюсти DRY да и KISS тоже где-то рядом.

Насколько я знаю, pathlib никаким образом не использует метаклассы. Поправьте, если я ошибаюсь. И даже если использует, то зачем в теории может понадобиться переопределять div на каком-нибудь PathType, а не на самом Path?

Откуда это взялось? Или я_точно_синглетон = dict() тоже синглетон? Класс это экземпляр класса type или одного из его наследников, не более.

В принципе любой объект питона уникален, так что я_точно_синглетон_dict() можно тоже назвать Cинглетоном. Единый объект класса доступен каждому объекту через type(obj) или через дандер и часто используется как хранитель единого состояния для порожденных объектов, что, собственно, является одной из причин создания Singleton. Потому я субъективно отношу классы к Singleton-объектам.

Класс это экземпляр класса type или одного из его наследников, не более.

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

type ничего не добавляет своим наследникам. Единственное что type.new делает, так это создаёт новый экземпляр переданного класса и заполняет его словарь, используя переданные параметры. Ну и валидацию выполняет, чтобы от некоторых типов не наследовались, но это не суть важно.

Еще раз. Класс не является наследником type, он является результатом работы функции конструктора класса. На примере:

 MyClass = type('Name', (__bases__,), {'mymethod': lambda *args, **kwargs: 'hi'})

В примере вызывается функция конструктор, создающая в локальной области видимости класс с именем "Name". Unbounded функции "mymethod" нет в методах базовых классов (если они переданы), и эта функция будет добавлена конструктором type к классу, да, грубо говоря добавлена в MyClass.__dict__, но если попробовать сделать это вручную получим'mappingproxy' object does not support item assignment. Так что это не напрямую "заполнение словаря".

Насколько я знаю, pathlib никаким образом не использует метаклассы. Поправьте, если я ошибаюсь.

Поправляю, Path наследован class PurePath(object), который, в свою очередь, ведет себя как metaclass, переопределяя __new__.

И даже если использует, то зачем в теории может понадобиться переопределять div на каком-нибудь PathType, а не на самом Path?

Переопределенный __div__ позволяет использовать синтаксис Path('root') / Path('folder'). В этом примере на команду __div__ происходит Path('root').parts + Path('folder').parts. Но Path в реальности это просто маска двух классов:cls = PureWindowsPath if os.name == 'nt' else PurePosixPath. Потому переопределив что-то у класса Path ничего не получим, надо переопределять у PureWindowsPath и PurePosixPath.

Но спасибо @ValeryIvanov кое что я узнал про конструктор в python3, от отличается от Python2, а я это упустил.

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

Еще раз. Класс не является наследником type, он является результатом работы функции конструктора класса.

Здесь я запутался в понятиях метакласс и класс. Но я имел ввиду то, что наследники класса type(то есть, метаклассы), это самые обычные классы, type никакой магии не делает.

Поправляю, Path наследован class PurePath(object), который, в свою очередь, ведет себя как metaclass, переопределяя new.

Переопределенный div позволяет использовать синтаксис Path('root') / Path('folder'). В этом примере на команду div происходит Path('root').parts + Path('folder').parts. Но Path в реальности это просто маска двух классов:cls = PureWindowsPath if os.name == 'nt' else PurePosixPath. Потому переопределив что-то у класса Path ничего не получим, надо переопределять у PureWindowsPath и PurePosixPath.

Формально, Path можно назвать метаклассом. Но чаще всего под метаклассами всё же имеют ввиду наследников класса type.
Но да, если рассматривать Path в таком ключе, то вы правы.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории