Комментарии 10
Классное объяснение, спасибо!
Это все здорово. Но это можно найти в любой книжке более-менее приличной по питону. А практическая польза? Вы можете привести хоть один кейс практического использования метаклассов?
"Ничего ты не знаешь, Джон Сноу"
Если рассматривать статью, как попытку разобраться в метаклассах. То зачет. Попытался.
Если рассматривать статью, как попытку разобраться в метаклассах. То мимо. Не разобрался.
Просто и понятно написано тут https://docs.python.org/3/reference/datamodel.html#metaclasses, но обычно это никто не читает.
Объекты класса как-то себя "проявляют" через методы и содержат атрибуты. Получить класс объекта можно командой type(obj).
Классы задают базовое поведение и задают шаблоны атрибутов для объектов класса. Любой класс это синглтон-объект, который как-то себя "проявляет" через методы и содержит атрибуты. Получить метакласс класса можно командой type(cls). Именно потому, что класс это тоже объект.
Метаклассы задают базовое поведение и задают шаблоны атрибутов для Классов, как объектов. Любой метакласс это синглтон-объект, который как-то себя "проявляет" через методы и содержит атрибуты. Получить Метакласс метакласса можно командой type(metacls). Именно потому, что метакласс - это тоже объект.
Можно продолжить. Создав мета мета класс, который будет задавать базовое поведение и задают шаблоны атрибутов для Мета классов, как объектов.
Фактически у нас есть только одно взаимоотношение. Класс --> Объект.
В списке выше это 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?
[del]
*ушел курить маны
Откуда это взялось? Или
я_точно_синглетон = 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 в таком ключе, то вы правы.
Основы метаклассов в Python: Простое понимание и примеры