Каковы (конкретные) варианты использования метаклассов?

118

У меня есть друг, который любит использовать метаклассы и регулярно предлагает их в качестве решения.

Я считаю, что вам почти никогда не нужно использовать метаклассы. Зачем? потому что я считаю, что если вы делаете что-то подобное с классом, вам, вероятно, следует делать это с объектом. И небольшой редизайн / рефакторинг здесь в порядке.

Возможность использовать метаклассы побудила многих людей во многих местах использовать классы в качестве некоего второстепенного объекта, что мне кажется катастрофой. Заменить ли программирование метапрограммированием? К сожалению, добавление декораторов классов сделало его еще более приемлемым.

Поэтому, пожалуйста, я отчаянно хочу узнать ваши допустимые (конкретные) варианты использования метаклассов в Python. Или чтобы понять, почему изменение классов лучше, чем изменение объектов.

Я начну:

Иногда при использовании сторонней библиотеки полезно иметь возможность изменять класс определенным образом.

(Это единственный случай, о котором я могу думать, и он не конкретный)

Али Афшар
источник
3
Это большой вопрос. Судя по приведенным ниже ответам, совершенно ясно, что не существует такой вещи, как конкретное использование метаклассов.
Маркус Оттоссон

Ответы:

25

У меня есть класс, который обрабатывает неинтерактивное построение графиков в качестве интерфейса для Matplotlib. Однако иногда хочется делать интерактивные построения. С помощью всего лишь пары функций я обнаружил, что могу увеличивать количество фигур, вызывать рисование вручную и т.д., но мне нужно было делать это до и после каждого вызова построения. Итак, чтобы создать как интерактивную оболочку для рисования, так и закадровую оболочку для рисования, я обнаружил, что более эффективно делать это с помощью метаклассов, обертывая соответствующие методы, чем делать что-то вроде:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

Этот метод не успевает за изменениями API и т. Д., Но тот, который выполняет итерацию по атрибутам класса __init__перед повторной установкой атрибутов класса, более эффективен и поддерживает актуальность:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

Конечно, могут быть более эффективные способы сделать это, но я считаю это эффективным. Конечно, это также можно было сделать в __new__или __init__, но это было решение, которое я нашел наиболее простым.

Matt
источник
103

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

Большинство метаклассов, которые я видел, делают одно из двух:

  1. Регистрация (добавление класса в структуру данных):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Всякий раз, когда вы создаете подкласс Model, ваш класс регистрируется в modelsсловаре:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    Это также можно сделать с помощью декораторов классов:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    Или с явной функцией регистрации:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

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

    В любом случае преимуществом метаклассов в этом случае является наследование, поскольку они работают для любых подклассов, тогда как другие решения работают только для подклассов, явно оформленных или зарегистрированных.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Рефакторинг (изменение атрибутов класса или добавление новых):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Всякий раз, когда вы подклассифицируете Modelи определяете некоторые Fieldатрибуты, они вводятся с их именами (например, для более информативных сообщений об ошибках) и группируются в _fieldsсловарь (для упрощения итерации без необходимости просматривать все атрибуты класса и все его базовые классы '' атрибуты каждый раз):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    Опять же, это можно сделать (без наследования) с помощью декоратора класса:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    Или явно:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    Хотя, в отличие от вашего отстаивания читабельного и поддерживаемого немета-программирования, это гораздо более громоздко, избыточно и подвержено ошибкам:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

Рассмотрев наиболее распространенные и конкретные варианты использования, единственные случаи, когда вам абсолютно НЕОБХОДИМО использовать метаклассы, - это когда вы хотите изменить имя класса или список базовых классов, потому что после определения эти параметры запекаются в классе, а декоратор или функция может их разбить.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

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

В любом случае, в Python 3 метаклассы также имеют __prepare__метод, который позволяет вам оценивать тело класса в сопоставлении, отличном от a dict, таким образом поддерживая упорядоченные атрибуты, перегруженные атрибуты и другие классные злые вещи:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

Вы можете утверждать, что упорядоченные атрибуты могут быть достигнуты с помощью счетчиков создания, а перегрузка может быть смоделирована с помощью аргументов по умолчанию:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

Помимо того, что он гораздо более уродлив, он также менее гибкий: что, если вам нужны упорядоченные буквальные атрибуты, такие как целые числа и строки? Что, если Noneдопустимое значение для x?

Вот творческий способ решения первой проблемы:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

А вот творческий способ решить вторую проблему:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

Но это намного, НАМНОГО вудуера, чем простой метакласс (особенно первый, который действительно плавит вам мозг). Я хочу сказать, что вы смотрите на метаклассы как на незнакомые и нелогичные, но вы также можете рассматривать их как следующий шаг эволюции языков программирования: вам просто нужно изменить свое мышление. В конце концов, вы, вероятно, могли бы делать все на C, включая определение структуры с указателями на функции и передачу ее в качестве первого аргумента ее функциям. Человек, впервые увидевший C ++, может спросить: «Что это за магия? Почему компилятор неявно передаетthisметодам, а не обычным и статическим функциям? Лучше быть явным и многословным в своих аргументах ". Но тогда объектно-ориентированное программирование станет гораздо более мощным, как только вы его получите; как и это, э ... квази-аспектно-ориентированное программирование, я думаю. понимаете метаклассы, они на самом деле очень простые, так почему бы не использовать их, когда это удобно?

И, наконец, метаклассы классные, а программирование должно доставлять удовольствие. Постоянное использование стандартных программных конструкций и шаблонов проектирования скучно, скучно и мешает вашему воображению. Живи немного! Вот метаметакласс специально для вас.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

редактировать

Это довольно старый вопрос, но он все еще получает положительные отзывы, поэтому я подумал, что добавлю ссылку на более подробный ответ. Если вы хотите узнать больше о метаклассах и их использовании, я только что опубликовал статью об этом здесь .

Дэн Гиттик
источник
5
Это отличный ответ, спасибо за время,
Чен А.
«... преимущество метаклассов в этом случае - наследование, поскольку они работают для любых подклассов» - не в Python 3, я полагаю? Я думаю, что это работает в Python 2 только потому, что любой дочерний класс наследует __metaclass__атрибут, но этот атрибут больше не является особенным в Python 3. Есть ли способ заставить эту вещь «дочерние классы также создаются родительским метаклассом» работать в Python 3 ?
ForceBru 05
2
Это верно и для Python 3, потому что класс B, унаследованный от A, метаклассом которого является M, также является типом M. Итак, когда B оценивается, M вызывается для его создания, и это эффективно позволяет вам "работать над любыми подклассами" (из A). Сказав это, Python 3.6 представил гораздо более простой init_subclassвариант, поэтому теперь вы можете манипулировать подклассами в базовом классе, и для этой цели больше не нужен метакласс.
Дэн Гиттик
Это великолепно, я прочитал так много сообщений в блогах о метаклассах, только этот объясняет плюсы и минусы и альтернативы метаклассам.
ospider
36

Цель метаклассов не в том, чтобы заменить различие класса / объекта на метакласс / класс, а в том, чтобы каким-то образом изменить поведение определений классов (и, следовательно, их экземпляров). По сути, это изменение поведения оператора класса способами, которые могут быть более полезными для вашего конкретного домена, чем стандарт по умолчанию. Я использовал их для следующих целей:

  • Отслеживание подклассов, обычно для регистрации обработчиков. Это удобно при использовании настройки стиля плагина, когда вы хотите зарегистрировать обработчик для определенной вещи, просто создав подклассы и установив несколько атрибутов класса. например. Предположим, вы пишете обработчик для различных музыкальных форматов, где каждый класс реализует соответствующие методы (теги play / get и т. д.) для своего типа. Добавление обработчика для нового типа становится:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    Затем метакласс поддерживает словарь и {'.mp3' : MP3File, ... }т.д. и создает объект соответствующего типа, когда вы запрашиваете обработчик через фабричную функцию.

  • Изменение поведения. Вы можете придать определенным атрибутам особое значение, что приведет к изменению их поведения при их наличии. Например, вы можете захотеть взглянуть на методы с именем _get_fooи _set_fooи прозрачно конвертировать их свойства. В качестве реального примера приведу рецепт, который я написал, чтобы дать больше определений структур в стиле C. Метакласс используется для преобразования объявленных элементов в строку формата структуры, обработки наследования и т. Д. И создания класса, способного работать с ним.

    Для других примеров реального мира, взгляните на различных ORMs, как SQLAlchemy в ОРМ или SQLObject . Опять же, цель состоит в том, чтобы интерпретировать определения (здесь определения столбцов SQL) с определенным значением.

Брайан
источник
3
Ну да, отслеживание подклассов. Но зачем вам это нужно? Ваш пример просто неявен для register_music_file (Mp3File, ['.mp3']), а явный способ более читабелен и удобен в обслуживании. Это пример плохих случаев, о которых я говорю.
Али Афшар,
Что касается случая ORM, вы говорите о способе определения таблиц на основе классов или о метаклассах для сопоставленных объектов. Поскольку SQLAlchemy может (справедливо) отображать любой класс (и я предполагаю, что он не использует метакласс для этого действия).
Али Афшар,
10
Я предпочитаю более декларативный стиль, чем требовать дополнительных методов регистрации для каждого подкласса - лучше, если все будет размещено в одном месте.
Брайан,
Что касается sqlalchemy, я думаю в основном о декларативном уровне, поэтому, возможно, sqlobject является лучшим примером. Однако используемые внутри метаклассы также являются примерами аналогичной переинтерпретации определенных атрибутов для объявления значения.
Брайан
2
Извините, один из моих conmments потерялся в сценарии тайм-аута SO. Я считаю классы для декларативного чуть ли не мерзостью. Я знаю, что людям это нравится, и это приемлемое поведение. Но (по опыту) я знаю, что это неприменимо в ситуации, когда вы хотите объявить вещи ООН. Отменить регистрацию класса сложно .
Али Афшар
17

Начнем с классической цитаты Тима Питера:

Метаклассы - это более глубокое волшебство, о котором 99% пользователей не должны беспокоиться. Если вы задаетесь вопросом, нужны ли они вам, то нет (люди, которым они действительно нужны, точно знают, что они им нужны, и не нуждаются в объяснении почему). Тим Питерс (clp post 2002-12-22)

Сказав это, я (периодически) сталкивался с истинным использованием метаклассов. На ум приходит Django, где все ваши модели наследуются от models.Model. models.Model, в свою очередь, совершает серьезную магию, чтобы обернуть ваши модели БД добротой Django ORM. Эта магия происходит с помощью метаклассов. Он создает всевозможные классы исключений, классы менеджеров и т. Д. И т. Д.

См. Django / db / models / base.py, class ModelBase () для начала истории.

Питер Роуэлл
источник
Ну да, я понимаю. Меня не интересует, «как» или «почему» использовать метаклассы, мне интересно, «кто» и «что». Я вижу, что ORM - частый случай. К сожалению, ORM Django довольно слабый по сравнению с SQLAlchemy, в котором меньше магии. Магия - это плохо, и метаклассы для этого действительно не нужны.
Али Афшар
9
Прочитав в прошлом цитату Тима Питерса, время показало, что его утверждение бесполезно. Только после исследования метаклассов Python здесь, в StackOverflow, стало ясно, как их реализовать. После того, как я заставил себя научиться писать и использовать метаклассы, их способности поразили меня и помогли мне лучше понять, как на самом деле работает Python. Классы могут предоставлять повторно используемый код, а метаклассы могут предоставлять повторно используемые улучшения для этих классов.
Ноктис Скайтауэр, 04
6

Метаклассы могут быть полезны для создания доменных языков в Python. Конкретными примерами являются Django, декларативный синтаксис схем базы данных SQLObject.

Базовый пример из Консервативного метакласса Яна Бикинга:

Метаклассы, которые я использовал, в первую очередь предназначены для поддержки своего рода декларативного стиля программирования. Например, рассмотрим схему проверки:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

Некоторые другие методы: Ингредиенты для создания DSL на Python (pdf).

Изменить (Али): я бы предпочел пример того, как это сделать с использованием коллекций и экземпляров. Важным фактом являются экземпляры, которые дают вам больше возможностей и устраняют причину использования метаклассов. Также стоит отметить, что в вашем примере используется смесь классов и экземпляров, что, безусловно, указывает на то, что вы не можете просто делать все это с помощью метаклассов. И создает действительно неоднородный способ сделать это.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

Это не идеально, но уже почти нет магии, нет необходимости в метаклассах и улучшена однородность.

JFS
источник
Спасибо за это. Это очень хороший пример варианта использования, который я считаю ненужным, уродливым и неуправляемым, что было бы проще на основе простого экземпляра коллекции (с вложенными коллекциями по мере необходимости).
Али Афшар,
1
@Ali A: вы можете предоставить конкретный пример параллельного сравнения декларативного синтаксиса через метаклассы и подхода, основанного на простом экземпляре коллекции.
jfs
@Ali A: вы можете отредактировать мой ответ на месте, чтобы добавить пример стиля коллекции.
jfs
Хорошо, сделал это. Извините, я немного тороплюсь сегодня, но постараюсь ответить на любые вопросы позже / завтра. Счастливых праздников!
Али Афшар
2
Второй пример уродлив, так как вам пришлось связать экземпляр валидатора с его именем. Чуть лучший способ сделать это - использовать словарь вместо списка, но тогда в классах Python просто синтаксический сахар для словаря, так почему бы не использовать классы? Вы также получаете бесплатную проверку имени, потому что младенцы Python не могут содержать пробелы или специальные символы, которые может содержать строка.
Ли Райан
6

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

Когда несколько классов используют одно и то же особое поведение, повторение __metaclass__=X, очевидно, лучше, чем повторение кода специального назначения и / или введение специальных общих суперклассов.

Но даже только один специальный класс и не обозримого расширения, __new__и __init__метакласса являются уборщик для инициализации переменных класса или других глобальных данных , чем перемешивая код специального назначения и нормальной defи classутверждения в теле определения класса.

user412090
источник
5

Единственный раз, когда я использовал метаклассы в Python, был при написании оболочки для Flickr API.

Моя цель состояла в том, чтобы очистить сайт api flickr и динамически создать полную иерархию классов, чтобы разрешить доступ к API с использованием объектов Python:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

Итак, в этом примере, поскольку я создал весь Python Flickr API с веб-сайта, я действительно не знаю определения классов во время выполнения. Возможность динамически генерировать типы была очень полезной.

Триптих
источник
2
Вы можете динамически генерировать типы без использования метаклассов. >>> help (type)
Али Афшар
8
Даже если вы не знаете об этом, вы находитесь с помощью метаклассов тогда. type - это метакласс, фактически самый распространенный. :-)
Veky 06
5

Я думал о том же самом вчера и полностью согласен. Сложности в коде, вызванные попытками сделать его более декларативным, как правило, делают кодовую базу сложнее поддерживать, труднее читать и, на мой взгляд, менее питоничен. Также обычно требуется много copy.copy () ing (для сохранения наследования и копирования от класса к экземпляру) и означает, что вам нужно искать во многих местах, что происходит (всегда смотреть от метакласса вверх), что противоречит также зерно питона. Я перебирал код formencode и sqlalchemy, чтобы увидеть, стоит ли такой декларативный стиль, а это явно нет. Такой стиль следует оставить дескрипторам (например, свойствам и методам) и неизменяемым данным. Ruby лучше поддерживает такие декларативные стили, и я рад, что основной язык Python не идет по этому пути.

Я вижу их использование для отладки, добавляю метакласс ко всем вашим базовым классам, чтобы получить более подробную информацию. Я также вижу их использование только в (очень) больших проектах, чтобы избавиться от некоторого шаблонного кода (но с потерей ясности). sqlalchemy, например , использует их где-либо еще, чтобы добавить определенный настраиваемый метод ко всем подклассам на основе значения атрибута в определении их класса, например, в игрушечном примере

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

может иметь метакласс, который генерирует метод в этом классе со специальными свойствами, основанными на «привет» (скажем, метод, добавляющий «привет» в конец строки). Для удобства сопровождения было бы хорошо убедиться, что вам не нужно писать метод в каждом подклассе, который вы создаете, вместо этого все, что вам нужно определить, это method_maker_value.

Тем не менее, необходимость в этом настолько редка, что сокращает количество набора текста, поэтому на самом деле это не стоит рассматривать, если у вас нет достаточно большой кодовой базы.

Дэвид Разник
источник
5

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

Тем не менее, в Smalltalk и Ruby может быть очень удобно изменять существующий класс, но Python не любит делать это напрямую.

Есть отличная статья DeveloperWorks о метаклассах в Python, которая может помочь. Статья Википедии также довольно хорошо.

Чарли Мартин
источник
1
Вам также не нужны объекты для объектно-ориентированного программирования - вы можете сделать это с помощью функций первого класса. Так что вам не нужно использовать объекты. Но они созданы для удобства. Так что я не уверен, что вы пытаетесь донести в первом абзаце.
Тайлер Кромптон,
1
Вернитесь к вопросу.
Чарли Мартин
4

Метаклассы не заменяют программирование! Это всего лишь уловка, которая может автоматизировать или сделать некоторые задачи более элегантными. Хорошим примером этого является библиотека подсветки синтаксиса Pygments . У него есть вызываемый класс, RegexLexerкоторый позволяет пользователю определять набор правил лексирования как регулярные выражения для класса. Метакласс используется для превращения определений в полезный синтаксический анализатор.

Они как соль; легко использовать слишком много.

Бенджамин Петерсон
источник
Что ж, на мой взгляд, случай с Pygments просто не нужен. Почему бы просто не создать простую коллекцию, такую ​​как dict, зачем заставлять класс делать это?
Али Афшар,
4
Потому что класс nice воплощает идею Lexer и имеет другие полезные методы, такие как guess_filename () и т. Д.
Бенджамин Петерсон
4

У некоторых библиотек графического интерфейса возникают проблемы, когда с ними пытаются взаимодействовать несколько потоков. tkinterодин из таких примеров; и хотя можно явно решить проблему с помощью событий и очередей, может быть гораздо проще использовать библиотеку таким образом, чтобы проблема полностью игнорировалась. Вот - магия метаклассов.

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

Один из замечательных аспектов threadboxзаключается в том, что ему все равно, какой класс он клонирует. Он предоставляет пример того, как все базовые классы могут быть затронуты метаклассом, если это необходимо. Еще одним преимуществом метаклассов является то, что они также работают на наследующих классах. Программы, которые пишут сами - почему бы и нет?

Ноктис Скайтауэр
источник
4

Единственный законный вариант использования метакласса - удерживать других любопытных разработчиков от прикосновения к вашему коду. Как только любопытный разработчик освоит метаклассы и начнет ковыряться в вашем, добавьте еще один или два уровня, чтобы не допустить их. Если это не сработает, начните использоватьtype.__new__ или, возможно, какую-нибудь схему с использованием рекурсивного метакласса.

(написано языком в щеку, но я видел такой вид обфускации. Django - прекрасный пример)

Майк А
источник
7
Я не уверен, что мотивация была такой же в Django.
Али Афшар
3

Я использовал метаклассы для предоставления некоторых атрибутов классам. Взять, к примеру:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

поместит атрибут name в каждый класс, для которого будет установлен метакласс, указывающий на NameClass.

hyperboreean
источник
5
Да, это работает. Вы также можете использовать суперкласс, который, по крайней мере, явный и отслеживаемый в коде. Ради интереса, для чего вы это использовали?
Али Афшар
2

Это второстепенное использование, но ... я нашел полезными метаклассы для вызова функции всякий раз, когда создается подкласс. Я кодифицировал это в метакласс, который ищет __initsubclass__атрибут: всякий раз, когда создается подкласс, вызываются все родительские классы, которые определяют этот метод __initsubclass__(cls, subcls). Это позволяет создать родительский класс, который затем регистрирует все подклассы в некотором глобальном реестре, выполняет инвариантные проверки подклассов всякий раз, когда они определены, выполняет операции позднего связывания и т. Д., И все это без необходимости вручную вызывать функции или создавать собственные метаклассы, которые выполнять каждую из этих отдельных обязанностей.

Имейте в виду, я постепенно пришел к осознанию того, что неявная магия такого поведения несколько нежелательна, поскольку это неожиданно, если смотреть на определение класса вне контекста ... и поэтому я отказался от использования этого решения для чего-либо серьезного, кроме инициализация __superатрибута для каждого класса и экземпляра.

Эли Коллинз
источник
1

Недавно мне пришлось использовать метакласс, чтобы помочь декларативно определить модель SQLAlchemy вокруг таблицы базы данных, заполненной данными переписи населения США из http://census.ire.org/data/bulkdata.html

IRE предоставляет оболочки баз данных для таблиц данных переписи, которые создают целочисленные столбцы в соответствии с соглашением об именах, принятым Бюро переписи населения p012015, p012016, p012017 и т. Д.

Я хотел: а) иметь доступ к этим столбцам с помощью model_instance.p012017 синтаксиса, б) четко указывать, что я делаю, и в) не нужно явно определять десятки полей в модели, поэтому я создал подклассы SQLAlchemy DeclarativeMetaдля итерации по диапазону столбцы и автоматически создают поля модели, соответствующие столбцам:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Затем я мог бы использовать этот метакласс для определения моей модели и получить доступ к автоматически пронумерованным полям модели:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...
Джеффри Хинг
источник
1

Кажется, здесь описывается законное использование - переписывание строк документации Python с помощью метакласса.

mistermarko
источник
0

Мне пришлось использовать их один раз для двоичного парсера, чтобы упростить его использование. Вы определяете класс сообщения с атрибутами полей, присутствующих в проводе. Их нужно было упорядочить так, как они были объявлены, чтобы построить из него окончательный формат проводов. Вы можете сделать это с помощью метаклассов, если используете dict упорядоченного пространства имен. Фактически, это в примерах для метаклассов:

https://docs.python.org/3/reference/datamodel.html#metaclass-example

Но в целом: очень внимательно оцените, действительно ли вам действительно нужна дополнительная сложность метаклассов.

GeeF
источник
0

ответ от @Dan Gittik классный

примеры в конце могут многое прояснить, я изменил его на python 3 и дал некоторые пояснения:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • все является объектом, поэтому класс есть объект
  • объект класса создается метаклассом
  • весь класс, унаследованный от типа, является метаклассом
  • метакласс может управлять созданием класса
  • метакласс также может управлять созданием метакласса (чтобы он мог зацикливаться вечно)
  • это метапрограммирование ... вы можете управлять системой типов во время работы
  • Опять же, все является объектом, это единая система, введите тип создания и введите экземпляр создания
foolcage
источник
0

Другой вариант использования - это когда вы хотите иметь возможность изменять атрибуты уровня класса и быть уверенным, что это влияет только на данный объект. На практике это подразумевает «слияние» фаз метаклассов и экземпляров классов, что приводит к тому, что вы имеете дело только с экземплярами классов их собственного (уникального) вида.

Мне также пришлось сделать это, когда (из соображений читабельности и полиморфизма ) мы хотели динамически определять property s, возвращаемые значения (могут) быть результатом вычислений на основе (часто меняющихся) атрибутов уровня экземпляра, что может быть выполнено только на уровне класса. , т.е. после создания метакласса и до создания экземпляра класса.

Keepalive
источник