Python Metaclass: Понимание 'with_metaclass()'

Я хочу спросить, что означает вызов with_metaclass() в определении класса.

например:.

class Foo(with_metaclass(Cls1, Cls2)):
  • Это особый случай, когда класс наследуется от метакласса?
  • Является ли новый класс также метаклассом?

Ответ 1

with_metaclass() - это функция фабрики служебных классов, предоставляемая библиотекой six, чтобы упростить разработку кода для Python 2 и 3.

Для временного метакласса используется небольшой запас руки (см. ниже), чтобы присоединить метакласс к обычному классу таким образом, чтобы он был совместим как с Python 2, так и с Python 3.

Цитирование из документации:

Создайте новый класс с базовым классом базы и метаклассом метакласса. Это предназначено для использования в объявлениях классов следующим образом:

from six import with_metaclass

class Meta(type):
    pass

class Base(object):
    pass

class MyClass(with_metaclass(Meta, Base)):
    pass

Это необходимо, поскольку синтаксис для присоединения метакласса изменился между Python 2 и 3:

Python 2:

class MyClass(object):
    __metaclass__ = Meta

Python 3:

class MyClass(metaclass=Meta):
    pass

Функция with_metaclass() использует тот факт, что метаклассы а) наследуются подклассами, и б) метакласс может использоваться для генерации новых классов и в) когда вы создаете подкласс из базового класса с метаклассом, создавая фактический объект подкласса делегируется метаклассу. Он эффективно создает новый временный базовый класс с временным метаклассом metaclass, который при использовании для создания подкласса заменяет временный базовый класс и комбинированный метакласс с выбранным метаклассом:

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(type):

        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)

        @classmethod
        def __prepare__(cls, name, this_bases):
            return meta.__prepare__(name, bases)
    return type.__new__(metaclass, 'temporary_class', (), {})

Разбиваем вышеперечисленное:

  • type.__new__(metaclass, 'temporary_class', (), {}) использует метакласс metaclass для создания нового объекта класса с именем temporary_class, который в противном случае будет полностью пустым. type.__new__(metaclass, ...) используется вместо metaclass(...), чтобы избежать использования специальной реализации metaclass.__new__(), которая необходима для того, чтобы на следующем шаге сработало.
  • Только в Python 3, когда temporary_class используется в качестве базового класса, Python сначала вызывает metaclass.__prepare__() (передавая имя производного класса, (temporary_class,) в качестве аргумента this_bases. Затем предполагаемый метакласс meta используется для вызовите meta.__prepare__(), игнорируя this_bases и передавая аргумент bases.
  • затем, после использования возвращаемого значения metaclass.__prepare__() в качестве базового пространства имен для атрибутов класса (или просто использования простого словаря в Python 2), Python вызывает metaclass.__new__() для создания фактического класса. Это снова передается (temporary_class,) как кортеж this_bases, но приведенный выше код игнорирует это и вместо этого использует bases, вызывая meta(name, bases, d) для создания нового производного класса.

В результате использование with_metaclass() дает вам новый объект класса без дополнительных базовых классов:

>>> class FooMeta(type): pass
...
>>> with_metaclass(FooMeta)  # returns a temporary_class object
<class '__main__.temporary_class'>
>>> type(with_metaclass(FooMeta))  # which has a custom metaclass
<class '__main__.metaclass'>
>>> class Foo(with_metaclass(FooMeta)): pass
...
>>> Foo.__mro__  # no extra base classes
(<class '__main__.Foo'>, <type 'object'>)
>>> type(Foo) # correct metaclass
<class '__main__.FooMeta'>

Ответ 2

UPDATE: функция six.with_metaclass() с тех пор была исправлена ​​с помощью варианта декоратора, то есть @six.add_metaclass(). Это обновление устраняет некоторые проблемы с mro, связанные с базовыми объектами. Новый декоратор будет применяться следующим образом:

import six

@six.add_metaclass(Meta)
class MyClass(Base):
    pass

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