У меня есть друг, который любит использовать метаклассы и регулярно предлагает их в качестве решения.
Я считаю, что вам почти никогда не нужно использовать метаклассы. Зачем? потому что я считаю, что если вы делаете что-то подобное с классом, вам, вероятно, следует делать это с объектом. И небольшой редизайн / рефакторинг здесь в порядке.
Возможность использовать метаклассы побудила многих людей во многих местах использовать классы в качестве некоего второстепенного объекта, что мне кажется катастрофой. Заменить ли программирование метапрограммированием? К сожалению, добавление декораторов классов сделало его еще более приемлемым.
Поэтому, пожалуйста, я отчаянно хочу узнать ваши допустимые (конкретные) варианты использования метаклассов в Python. Или чтобы понять, почему изменение классов лучше, чем изменение объектов.
Я начну:
Иногда при использовании сторонней библиотеки полезно иметь возможность изменять класс определенным образом.
(Это единственный случай, о котором я могу думать, и он не конкретный)
Ответы:
У меня есть класс, который обрабатывает неинтерактивное построение графиков в качестве интерфейса для Matplotlib. Однако иногда хочется делать интерактивные построения. С помощью всего лишь пары функций я обнаружил, что могу увеличивать количество фигур, вызывать рисование вручную и т.д., но мне нужно было делать это до и после каждого вызова построения. Итак, чтобы создать как интерактивную оболочку для рисования, так и закадровую оболочку для рисования, я обнаружил, что более эффективно делать это с помощью метаклассов, обертывая соответствующие методы, чем делать что-то вроде:
Этот метод не успевает за изменениями API и т. Д., Но тот, который выполняет итерацию по атрибутам класса
__init__
перед повторной установкой атрибутов класса, более эффективен и поддерживает актуальность:Конечно, могут быть более эффективные способы сделать это, но я считаю это эффективным. Конечно, это также можно было сделать в
__new__
или__init__
, но это было решение, которое я нашел наиболее простым.источник
Недавно мне задали тот же вопрос, и я дал несколько ответов. Я надеюсь, что можно будет возобновить эту ветку, так как я хотел подробно остановиться на некоторых из упомянутых вариантов использования и добавить несколько новых.
Большинство метаклассов, которые я видел, делают одно из двух:
Регистрация (добавление класса в структуру данных):
Всякий раз, когда вы создаете подкласс
Model
, ваш класс регистрируется вmodels
словаре:Это также можно сделать с помощью декораторов классов:
Или с явной функцией регистрации:
На самом деле, это почти то же самое: вы отрицательно упоминаете декораторы классов, но на самом деле это не более чем синтаксический сахар для вызова функции в классе, поэтому в этом нет никакого волшебства.
В любом случае преимуществом метаклассов в этом случае является наследование, поскольку они работают для любых подклассов, тогда как другие решения работают только для подклассов, явно оформленных или зарегистрированных.
Рефакторинг (изменение атрибутов класса или добавление новых):
Всякий раз, когда вы подклассифицируете
Model
и определяете некоторыеField
атрибуты, они вводятся с их именами (например, для более информативных сообщений об ошибках) и группируются в_fields
словарь (для упрощения итерации без необходимости просматривать все атрибуты класса и все его базовые классы '' атрибуты каждый раз):Опять же, это можно сделать (без наследования) с помощью декоратора класса:
Или явно:
Хотя, в отличие от вашего отстаивания читабельного и поддерживаемого немета-программирования, это гораздо более громоздко, избыточно и подвержено ошибкам:
Рассмотрев наиболее распространенные и конкретные варианты использования, единственные случаи, когда вам абсолютно НЕОБХОДИМО использовать метаклассы, - это когда вы хотите изменить имя класса или список базовых классов, потому что после определения эти параметры запекаются в классе, а декоратор или функция может их разбить.
Это может быть полезно в фреймворках для выдачи предупреждений всякий раз, когда определены классы с похожими именами или неполными деревьями наследования, но я не могу придумать причину, помимо троллинга, для фактического изменения этих значений. Может быть, Дэвид Бизли сможет.
В любом случае, в Python 3 метаклассы также имеют
__prepare__
метод, который позволяет вам оценивать тело класса в сопоставлении, отличном от adict
, таким образом поддерживая упорядоченные атрибуты, перегруженные атрибуты и другие классные злые вещи:Вы можете утверждать, что упорядоченные атрибуты могут быть достигнуты с помощью счетчиков создания, а перегрузка может быть смоделирована с помощью аргументов по умолчанию:
Помимо того, что он гораздо более уродлив, он также менее гибкий: что, если вам нужны упорядоченные буквальные атрибуты, такие как целые числа и строки? Что, если
None
допустимое значение дляx
?Вот творческий способ решения первой проблемы:
А вот творческий способ решить вторую проблему:
Но это намного, НАМНОГО вудуера, чем простой метакласс (особенно первый, который действительно плавит вам мозг). Я хочу сказать, что вы смотрите на метаклассы как на незнакомые и нелогичные, но вы также можете рассматривать их как следующий шаг эволюции языков программирования: вам просто нужно изменить свое мышление. В конце концов, вы, вероятно, могли бы делать все на C, включая определение структуры с указателями на функции и передачу ее в качестве первого аргумента ее функциям. Человек, впервые увидевший C ++, может спросить: «Что это за магия? Почему компилятор неявно передает
this
методам, а не обычным и статическим функциям? Лучше быть явным и многословным в своих аргументах ". Но тогда объектно-ориентированное программирование станет гораздо более мощным, как только вы его получите; как и это, э ... квази-аспектно-ориентированное программирование, я думаю. понимаете метаклассы, они на самом деле очень простые, так почему бы не использовать их, когда это удобно?И, наконец, метаклассы классные, а программирование должно доставлять удовольствие. Постоянное использование стандартных программных конструкций и шаблонов проектирования скучно, скучно и мешает вашему воображению. Живи немного! Вот метаметакласс специально для вас.
редактировать
Это довольно старый вопрос, но он все еще получает положительные отзывы, поэтому я подумал, что добавлю ссылку на более подробный ответ. Если вы хотите узнать больше о метаклассах и их использовании, я только что опубликовал статью об этом здесь .
источник
__metaclass__
атрибут, но этот атрибут больше не является особенным в Python 3. Есть ли способ заставить эту вещь «дочерние классы также создаются родительским метаклассом» работать в Python 3 ?init_subclass
вариант, поэтому теперь вы можете манипулировать подклассами в базовом классе, и для этой цели больше не нужен метакласс.Цель метаклассов не в том, чтобы заменить различие класса / объекта на метакласс / класс, а в том, чтобы каким-то образом изменить поведение определений классов (и, следовательно, их экземпляров). По сути, это изменение поведения оператора класса способами, которые могут быть более полезными для вашего конкретного домена, чем стандарт по умолчанию. Я использовал их для следующих целей:
Отслеживание подклассов, обычно для регистрации обработчиков. Это удобно при использовании настройки стиля плагина, когда вы хотите зарегистрировать обработчик для определенной вещи, просто создав подклассы и установив несколько атрибутов класса. например. Предположим, вы пишете обработчик для различных музыкальных форматов, где каждый класс реализует соответствующие методы (теги play / get и т. д.) для своего типа. Добавление обработчика для нового типа становится:
Затем метакласс поддерживает словарь и
{'.mp3' : MP3File, ... }
т.д. и создает объект соответствующего типа, когда вы запрашиваете обработчик через фабричную функцию.Изменение поведения. Вы можете придать определенным атрибутам особое значение, что приведет к изменению их поведения при их наличии. Например, вы можете захотеть взглянуть на методы с именем
_get_foo
и_set_foo
и прозрачно конвертировать их свойства. В качестве реального примера приведу рецепт, который я написал, чтобы дать больше определений структур в стиле C. Метакласс используется для преобразования объявленных элементов в строку формата структуры, обработки наследования и т. Д. И создания класса, способного работать с ним.Для других примеров реального мира, взгляните на различных ORMs, как SQLAlchemy в ОРМ или SQLObject . Опять же, цель состоит в том, чтобы интерпретировать определения (здесь определения столбцов SQL) с определенным значением.
источник
Начнем с классической цитаты Тима Питера:
Сказав это, я (периодически) сталкивался с истинным использованием метаклассов. На ум приходит Django, где все ваши модели наследуются от models.Model. models.Model, в свою очередь, совершает серьезную магию, чтобы обернуть ваши модели БД добротой Django ORM. Эта магия происходит с помощью метаклассов. Он создает всевозможные классы исключений, классы менеджеров и т. Д. И т. Д.
См. Django / db / models / base.py, class ModelBase () для начала истории.
источник
Метаклассы могут быть полезны для создания доменных языков в Python. Конкретными примерами являются Django, декларативный синтаксис схем базы данных SQLObject.
Базовый пример из Консервативного метакласса Яна Бикинга:
Некоторые другие методы: Ингредиенты для создания DSL на Python (pdf).
Изменить (Али): я бы предпочел пример того, как это сделать с использованием коллекций и экземпляров. Важным фактом являются экземпляры, которые дают вам больше возможностей и устраняют причину использования метаклассов. Также стоит отметить, что в вашем примере используется смесь классов и экземпляров, что, безусловно, указывает на то, что вы не можете просто делать все это с помощью метаклассов. И создает действительно неоднородный способ сделать это.
Это не идеально, но уже почти нет магии, нет необходимости в метаклассах и улучшена однородность.
источник
Разумный шаблон использования метакласса - это делать что-то один раз, когда класс определен, а не повторно при создании экземпляра того же класса.
Когда несколько классов используют одно и то же особое поведение, повторение
__metaclass__=X
, очевидно, лучше, чем повторение кода специального назначения и / или введение специальных общих суперклассов.Но даже только один специальный класс и не обозримого расширения,
__new__
и__init__
метакласса являются уборщик для инициализации переменных класса или других глобальных данных , чем перемешивая код специального назначения и нормальнойdef
иclass
утверждения в теле определения класса.источник
Единственный раз, когда я использовал метаклассы в Python, был при написании оболочки для Flickr API.
Моя цель состояла в том, чтобы очистить сайт api flickr и динамически создать полную иерархию классов, чтобы разрешить доступ к API с использованием объектов Python:
Итак, в этом примере, поскольку я создал весь Python Flickr API с веб-сайта, я действительно не знаю определения классов во время выполнения. Возможность динамически генерировать типы была очень полезной.
источник
Я думал о том же самом вчера и полностью согласен. Сложности в коде, вызванные попытками сделать его более декларативным, как правило, делают кодовую базу сложнее поддерживать, труднее читать и, на мой взгляд, менее питоничен. Также обычно требуется много copy.copy () ing (для сохранения наследования и копирования от класса к экземпляру) и означает, что вам нужно искать во многих местах, что происходит (всегда смотреть от метакласса вверх), что противоречит также зерно питона. Я перебирал код formencode и sqlalchemy, чтобы увидеть, стоит ли такой декларативный стиль, а это явно нет. Такой стиль следует оставить дескрипторам (например, свойствам и методам) и неизменяемым данным. Ruby лучше поддерживает такие декларативные стили, и я рад, что основной язык Python не идет по этому пути.
Я вижу их использование для отладки, добавляю метакласс ко всем вашим базовым классам, чтобы получить более подробную информацию. Я также вижу их использование только в (очень) больших проектах, чтобы избавиться от некоторого шаблонного кода (но с потерей ясности). sqlalchemy, например , использует их где-либо еще, чтобы добавить определенный настраиваемый метод ко всем подклассам на основе значения атрибута в определении их класса, например, в игрушечном примере
может иметь метакласс, который генерирует метод в этом классе со специальными свойствами, основанными на «привет» (скажем, метод, добавляющий «привет» в конец строки). Для удобства сопровождения было бы хорошо убедиться, что вам не нужно писать метод в каждом подклассе, который вы создаете, вместо этого все, что вам нужно определить, это method_maker_value.
Тем не менее, необходимость в этом настолько редка, что сокращает количество набора текста, поэтому на самом деле это не стоит рассматривать, если у вас нет достаточно большой кодовой базы.
источник
Вам никогда не понадобится использовать метакласс, поскольку вы всегда можете создать класс, который будет делать то, что вы хотите, используя наследование или агрегирование класса, который вы хотите изменить.
Тем не менее, в Smalltalk и Ruby может быть очень удобно изменять существующий класс, но Python не любит делать это напрямую.
Есть отличная статья DeveloperWorks о метаклассах в Python, которая может помочь. Статья Википедии также довольно хорошо.
источник
Метаклассы не заменяют программирование! Это всего лишь уловка, которая может автоматизировать или сделать некоторые задачи более элегантными. Хорошим примером этого является библиотека подсветки синтаксиса Pygments . У него есть вызываемый класс,
RegexLexer
который позволяет пользователю определять набор правил лексирования как регулярные выражения для класса. Метакласс используется для превращения определений в полезный синтаксический анализатор.Они как соль; легко использовать слишком много.
источник
У некоторых библиотек графического интерфейса возникают проблемы, когда с ними пытаются взаимодействовать несколько потоков.
tkinter
один из таких примеров; и хотя можно явно решить проблему с помощью событий и очередей, может быть гораздо проще использовать библиотеку таким образом, чтобы проблема полностью игнорировалась. Вот - магия метаклассов.Возможность динамически переписывать всю библиотеку таким образом, чтобы она работала должным образом в многопоточном приложении, может быть чрезвычайно полезной в некоторых обстоятельствах. Модуль safetkinter делает это с помощью метакласса, предоставляемого модулем threadbox - события и очереди не нужны.
Один из замечательных аспектов
threadbox
заключается в том, что ему все равно, какой класс он клонирует. Он предоставляет пример того, как все базовые классы могут быть затронуты метаклассом, если это необходимо. Еще одним преимуществом метаклассов является то, что они также работают на наследующих классах. Программы, которые пишут сами - почему бы и нет?источник
Единственный законный вариант использования метакласса - удерживать других любопытных разработчиков от прикосновения к вашему коду. Как только любопытный разработчик освоит метаклассы и начнет ковыряться в вашем, добавьте еще один или два уровня, чтобы не допустить их. Если это не сработает, начните использовать
type.__new__
или, возможно, какую-нибудь схему с использованием рекурсивного метакласса.(написано языком в щеку, но я видел такой вид обфускации. Django - прекрасный пример)
источник
Я использовал метаклассы для предоставления некоторых атрибутов классам. Взять, к примеру:
поместит атрибут name в каждый класс, для которого будет установлен метакласс, указывающий на NameClass.
источник
Это второстепенное использование, но ... я нашел полезными метаклассы для вызова функции всякий раз, когда создается подкласс. Я кодифицировал это в метакласс, который ищет
__initsubclass__
атрибут: всякий раз, когда создается подкласс, вызываются все родительские классы, которые определяют этот метод__initsubclass__(cls, subcls)
. Это позволяет создать родительский класс, который затем регистрирует все подклассы в некотором глобальном реестре, выполняет инвариантные проверки подклассов всякий раз, когда они определены, выполняет операции позднего связывания и т. Д., И все это без необходимости вручную вызывать функции или создавать собственные метаклассы, которые выполнять каждую из этих отдельных обязанностей.Имейте в виду, я постепенно пришел к осознанию того, что неявная магия такого поведения несколько нежелательна, поскольку это неожиданно, если смотреть на определение класса вне контекста ... и поэтому я отказался от использования этого решения для чего-либо серьезного, кроме инициализация
__super
атрибута для каждого класса и экземпляра.источник
Недавно мне пришлось использовать метакласс, чтобы помочь декларативно определить модель SQLAlchemy вокруг таблицы базы данных, заполненной данными переписи населения США из http://census.ire.org/data/bulkdata.html
IRE предоставляет оболочки баз данных для таблиц данных переписи, которые создают целочисленные столбцы в соответствии с соглашением об именах, принятым Бюро переписи населения p012015, p012016, p012017 и т. Д.
Я хотел: а) иметь доступ к этим столбцам с помощью
model_instance.p012017
синтаксиса, б) четко указывать, что я делаю, и в) не нужно явно определять десятки полей в модели, поэтому я создал подклассы SQLAlchemyDeclarativeMeta
для итерации по диапазону столбцы и автоматически создают поля модели, соответствующие столбцам:Затем я мог бы использовать этот метакласс для определения моей модели и получить доступ к автоматически пронумерованным полям модели:
источник
Кажется, здесь описывается законное использование - переписывание строк документации Python с помощью метакласса.
источник
Мне пришлось использовать их один раз для двоичного парсера, чтобы упростить его использование. Вы определяете класс сообщения с атрибутами полей, присутствующих в проводе. Их нужно было упорядочить так, как они были объявлены, чтобы построить из него окончательный формат проводов. Вы можете сделать это с помощью метаклассов, если используете dict упорядоченного пространства имен. Фактически, это в примерах для метаклассов:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
Но в целом: очень внимательно оцените, действительно ли вам действительно нужна дополнительная сложность метаклассов.
источник
ответ от @Dan Gittik классный
примеры в конце могут многое прояснить, я изменил его на python 3 и дал некоторые пояснения:
источник
Другой вариант использования - это когда вы хотите иметь возможность изменять атрибуты уровня класса и быть уверенным, что это влияет только на данный объект. На практике это подразумевает «слияние» фаз метаклассов и экземпляров классов, что приводит к тому, что вы имеете дело только с экземплярами классов их собственного (уникального) вида.
Мне также пришлось сделать это, когда (из соображений читабельности и полиморфизма ) мы хотели динамически определять
property
s, возвращаемые значения (могут) быть результатом вычислений на основе (часто меняющихся) атрибутов уровня экземпляра, что может быть выполнено только на уровне класса. , т.е. после создания метакласса и до создания экземпляра класса.источник