Які існують конкретні випадки використання для метакласів?


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__, але це було рішення , яке я знайшов найпростіший.


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

2
Це справедливо і для Python 3, тому що клас B, успадкований від A, метакласом якого є M, також є типом M. Отже, коли B оцінюється, M викликається, щоб створити його, і це ефективно дозволяє вам "працювати над будь-якими підкласами" (з A). Сказавши це, Python 3.6 представив набагато простіше init_subclass, тож тепер ви можете маніпулювати підкласами в базовому класі, і більше не потрібно метакласу для цієї мети.
Dan Gittik

Це геніально, я читав так багато публікацій у блогах про метакласи, тільки цей знає плюси та мінуси та альтернативи метакласу.
ospider

36

Мета метакласів не замінити відмінність класу / об'єкта метакласом / класом - це певним чином змінити поведінку визначень класів (і, отже, їх екземплярів). Ефективно це змінити поведінку класового оператора способами, які можуть бути кориснішими для вашого конкретного домену, ніж за замовчуванням. Я використовував їх:

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

    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і прозоро конвертувати їх властивості. Як приклад у реальному світі, ось рецепт, який я написав, щоб дати більше С-подібних структурних визначень. Метаклас використовується для перетворення оголошених елементів у струнковий формат, обробку спадщини тощо, та створення класу, здатного з ним працювати.

    Для інших прикладів у реальному світі перегляньте різні ORM, наприклад ORM sqlalchemy або sqlobject . Знову ж таки, мета - інтерпретувати визначення (тут визначення SQL стовпців) з певним значенням.


3
Ну так, відстеження підкласів. Але чому б ти цього хотів? Ваш приклад є просто неявним для register_music_file (Mp3File, ['.mp3']), і явний спосіб є більш читабельним та доступним для оновлення. Це приклад поганих випадків, про які я говорю.
Алі Афшар

Що стосується випадку ORM, ви говорите про класний спосіб визначення таблиць або про метакласи на відображених об’єктах. Тому що SQLAlchemy може (справедливо) відображати будь-який клас (і я припускаю, що він не використовує метаклас для цієї діяльності).
Алі Афшар

10
Я віддаю перевагу більш декларативному стилю, ніж вимагаю додаткових методів реєстрації для кожного підкласу - краще, якщо все буде загорнуто в одне місце.
Брайан

Для sqlalchemy я думаю переважно про декларативний шар, тому, можливо, sqlobject є кращим прикладом. Однак внутрішні метакласи також є прикладами подібного переосмислення конкретних атрибутів для декларування сенсу.
Брайан

2
Вибачте, один із моїх застережень загубився в сценарії SO. Я вважаю, що заняття з декларативної легіонізованої шкоди. Я знаю, що люди це люблять, і це прийнята поведінка. Але (з досвіду) я знаю, що він непридатний у ситуації, коли ти хочеш декларувати ООН. Скасувати реєстрацію класу важко .
Алі Афшар

17

Почнемо з класичної цитати Тіма Петра:

Метакласи - це глибша магія, ніж коли-небудь повинні турбуватися 99% користувачів. Якщо вам цікаво, чи потрібні вони вам, ви цього не робите (люди, які насправді їх потребують, з упевненістю знають, що вони їм потрібні, і не потребують пояснень, чому це потрібно). Тім Пітерс (клп пост 2002-12-22)

Сказавши це, я (періодично) натрапляю на справжнє використання метакласів. Той, що спадає на думку, знаходиться в Джанго, де всі ваші моделі успадковують від моделей. Модель. Моделі, в свою чергу, робить деякі серйозні магії, щоб обернути ваші моделі БД добротою ORM Джанго. Ця магія відбувається за допомогою метакласів. Це створює всілякі класи виключень, класи менеджерів тощо.

Дивіться django / db / models / base.py, клас ModelBase () для початку розповіді.


Ну так, я бачу сенс. Мені не цікаво "як" чи "чому" використовувати метакласи, мені цікаво "хто" і "що". ОРМ є поширеним випадком тут, я бачу. На жаль, ORM Джанго досить бідний порівняно з SQLAlchemy, який має менше магії. Магія погана, а метакласи для цього дійсно не потрібні.
Алі Афшар

9
Прочитавши цитату Тіма Пітерса в минулому, час показав, що його висловлювання досить корисне. Поки не було досліджено метакласи Python тут на StackOverflow, стало зрозуміло, як їх навіть реалізувати. Після того, як змусив себе навчитися писати та використовувати метакласи, їхні здібності здивували мене і дали мені набагато краще розуміння того, як Python працює насправді. Класи можуть надавати код багаторазового використання, а метакласи можуть надавати поліпшення для багаторазового використання для цих класів.
Noctis Skytower

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,])
]

Це не ідеально, але вже існує майже нульова магія, немає необхідності в метакласах і вдосконалена однаковість.


Дякую за це Це дуже хороший приклад використання, я думаю, це зайве, некрасиве та некероване, що було б простіше на основі простого примірника колекції (з вкладеними колекціями, як потрібно).
Алі Афшар

1
@Ali A: Ви можете надати конкретний приклад порівнювання декларативної синтаксису через метакласи та підходу, заснованого на простому екземплярі колекції.
jfs

@Ali A: ви можете змінити мою відповідь на місці, щоб додати приклад стилю колекції.
jfs

Гаразд зробив це. На жаль, сьогодні я поспішаю, але спробую відповісти на будь-які запитання пізніше / завтра. Щасливих свят!
Алі Афшар

2
Другий приклад некрасивий, оскільки вам довелося зв'язати екземпляр валідатора з їх іменем. Трохи кращий спосіб зробити це - використовувати словник замість списку, але тоді, у класах python, це лише синтаксичний цукор для словника, так чому б не використовувати класи? Ви також отримуєте безкоштовну перевірку імен, тому що немовлята python не можуть містити пробілів або спеціальних символів, які могли б робити рядки.
Лежи Райан

6

Розумна модель використання метакласу - це щось робити один раз, коли клас визначається, а не повторно, коли той самий клас інстанціюється.

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

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


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

Отже, у цьому прикладі, оскільки я генерував цілий API Python Flickr з веб-сайту, я дійсно не знаю визначень класів під час виконання. Можливість динамічно генерувати типи було дуже корисно.


2
Ви можете динамічно генерувати типи, не використовуючи метакласи. >>> допомога (тип)
Алі Афшар

8
Навіть якщо ви не знаєте про це, ви перебуваєте з допомогою метаклассом тоді. тип - це метаклас, насправді найпоширеніший. :-)
Veky

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

Деякі бібліотеки GUI мають проблеми, коли кілька потоків намагаються взаємодіяти з ними. tkinterє одним з таких прикладів; і хоча явно можна вирішити проблему з подіями та чергами, використовувати бібліотеку можна набагато простіше таким чином, що взагалі ігнорує проблему. Ось - магія метакласів.

Можливість динамічно переписати всю бібліотеку, щоб вона працювала належним чином, як очікувалося, у багатопотоковому додатку може бути дуже корисною в деяких обставинах. Модуль safetkinter робить це за допомогою метакласу, наданого модулем потокової скриньки - події та черги не потрібні.

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


4

Єдиний законний випадок використання метакласу - це запобігати торкання інших кодів розробників. Як тільки тужливий розробник освоює метакласи і починає кидатись разом з вашими, киньте ще один або два рівні, щоб їх не було. Якщо це не працює, почніть використовувати type.__new__або, можливо, якусь схему, використовуючи рекурсивний метаклас.

(письмовою мовою щоки, але я бачив подібну обдумування. Джанго - прекрасний приклад)


7
Я не впевнений, що мотивація була однаковою у Джанго.
Алі Афшар

3

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

Вони як сіль; легко використовувати занадто багато.


Ну, на мою думку, випадок Pygments просто зайвий. Чому б просто не мати просту колекцію, як диктант, чому змусити клас це робити?
Алі Афшар

4
Тому що клас приємно обробляє ідею Лексера і має інші корисні методи, такі як гадка_файла () тощо.
Бенджамін Петерсон,

3

Те, як я використовував метакласи, полягав у наданні класів атрибутів. Візьмемо для прикладу:

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

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


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

    ...


0

Довелося використовувати їх один раз для двійкового аналізатора, щоб полегшити його використання. Ви визначаєте клас повідомлення з атрибутами полів, присутніх на дроті. Їх потрібно було замовити таким чином, як вони були оголошені для побудови з нього остаточного формату дроту. Це можна зробити з метакласами, якщо ви використовуєте впорядкований дикт простору імен. Насправді, це у прикладах для Metaclasses:

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

Але в цілому: Дуже уважно оцініть, чи дійсно вам потрібна додаткова складність метакласів.


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
  • все є об'єктом, тому клас є об'єктом
  • об’єкт класу створюється метакласом
  • весь клас, успадкований від типу, є метакласом
  • метаклас може контролювати створення класів
  • metaclass також може керувати створенням метакласу (щоб він міг циклічно)
  • це метапрограмування ... ви могли керувати системою типів під час роботи
  • Знову ж таки, все є об'єктом, це єдина система, тип створення типу та тип створення екземпляра

0

Інший випадок використання - це коли ви хочете мати змогу змінювати атрибути рівня класу та бути впевненими, що це впливає лише на об'єкт, що знаходиться у вас. На практиці це передбачає "злиття" фаз метакласів і класів, таким чином, змушуючи вас мати справу лише з класовими екземплярами власного (унікального) виду.

Я також повинен був це зробити, коли (з точки зору читаності та поліморфізму ) ми хотіли динамічно визначити property s, які повернені значення (можуть) бути результатами обчислень на основі (часто мінливих) атрибутів рівня екземпляра, що можна зробити лише на рівні класу , тобто після ідентифікації метакласу та перед інстанціацією класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.