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

StackOverflow https://stackoverflow.com/questions/392160

  •  23-08-2019
  •  | 
  •  

Вопрос

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

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

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

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

Я начну:

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

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

Это было полезно?

Решение

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

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__, но это было решение, которое я нашел самым простым.

Другие советы

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

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

  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__ метод, который позволяет вам преобразовать тело класса в отображение, отличное от 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 к методам, но не к обычным и статическим функциям?Лучше быть ясным и многословным в своих аргументах".Но тогда объектно-ориентированное программирование становится намного мощнее, как только вы его освоите;и это тоже, э-э-э...квази-аспектно-ориентированное программирование, я полагаю.И как только вы поймете, что такое метаклассы, они на самом деле очень просты, так почему бы не использовать их, когда это удобно?

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

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

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

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

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

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

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

    Для получения других реальных примеров взгляните на различные ORM, например sqlalchemy's ОРМ или квадратный объект.Опять же, цель состоит в том, чтобы интерпретировать определения (здесь определения столбцов SQL) с определенным значением.

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

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

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

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

Метаклассы могут быть удобны для построения предметно-ориентированных языков на 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,])
]

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

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

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

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

Единственный раз, когда я использовал метаклассы в 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 с веб-сайта, я действительно не знаю определений классов во время выполнения.Возможность динамически генерировать типы была очень полезна.

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

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

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

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

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

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

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

Там есть отличный Статья developerWorks о метаклассификации в Python, которая могла бы помочь.В Статья в Википедии это тоже довольно хорошо.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я хотел а) иметь возможность доступа к этим столбцам с помощью model_instance.p012017 синтаксис, б) быть достаточно ясным в отношении того, что я делал, и в) не нужно явно определять десятки полей в модели, поэтому я подкласс sqlalchemy's 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

    ...

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

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

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

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

ответ от @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

  • все является объектом, поэтому класс является объектом
  • объект класса создается с помощью metaclass
  • весь класс , наследуемый от type, является метаклассом
  • метакласс мог бы управлять созданием класса
  • метакласс также мог управлять созданием метакласса (так что он мог зацикливаться вечно).
  • это метапрограммирование...вы могли бы управлять системой типов во время выполнения
  • опять же, все является объектом, это единая система, введите create type и введите create instance

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top