Что такое метаклассы в Python?

avatar
e-satis
19 сентября 2008 в 06:10
913398
25
6293

Что такое метаклассы в Python и для чего мы их используем?

Источник

Ответы (25)

avatar
Thomas Wouters
19 сентября 2008 в 07:01
3272

Метакласс - это класс класса. Класс определяет, как ведет себя экземпляр класса (то есть объект), в то время как метакласс определяет, как ведет себя класс. Класс - это экземпляр метакласса.

Хотя в Python вы можете использовать произвольные вызываемые объекты для метаклассов (как показывает Jerub), лучший подход - сделать его самим фактическим классом. type - обычный метакласс в Python. type сам по себе является классом, и это отдельный тип. Вы не сможете воссоздать что-то вроде type только на Python, но Python немного обманывает. Чтобы создать свой собственный метакласс в Python, вы действительно хотите создать подкласс type.

Метакласс чаще всего используется как фабрика классов. Когда вы создаете объект, вызывая класс, Python создает новый класс (когда он выполняет оператор class), вызывая метакласс. Таким образом, в сочетании с обычными методами __init__ и __new__ метаклассы позволяют вам делать «дополнительные вещи» при создании класса, например регистрировать новый класс в каком-либо реестре или полностью заменять класс чем-то другим.

Когда выполняется инструкция class, Python сначала выполняет тело инструкции class как обычный блок кода. Результирующее пространство имен (dict) содержит атрибуты будущего класса. Метакласс определяется путем просмотра базовых классов будущего класса (метаклассы наследуются), атрибута __metaclass__ будущего класса (если есть) или глобальной переменной __metaclass__. Затем вызывается метакласс с именем, базами и атрибутами класса для его создания.

Однако метаклассы фактически определяют тип класса, а не только его фабрику, поэтому с ними можно делать гораздо больше. Вы можете, например, определить обычные методы в метаклассе. Эти метаклассы-методы похожи на методы классов в том, что они могут быть вызваны в классе без экземпляра, но они также не похожи на методы классов в том, что они не могут быть вызваны в экземпляре класса. type.__subclasses__() - это пример метода в метаклассе type. Вы также можете определить обычные «магические» методы, такие как __add__, __iter__ и __getattr__, чтобы реализовать или изменить поведение класса.

Вот агрегированный пример кусочков и частей:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
pppery
3 августа 2017 в 14:34
17

class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b

Holle van
18 сентября 2018 в 23:24
30

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

Ciasto piekarz
29 ноября 2018 в 00:59
4

Не следует ли вызывать unregister () экземпляром класса Example?

BlackShift
1 мая 2019 в 08:36
16

Обратите внимание, что __metaclass__ не поддерживается в Python 3. В Python 3 используйте class MyObject(metaclass=MyType), см. python.org/dev/peps/pep-3115 и ответ ниже.

kapad
29 августа 2019 в 06:56
1

The metaclass is determined by looking at the baseclasses of the class-to-be (metaclasses are inherited), at the __metaclass__ attribute of the class-to-be (if any) or the __metaclass__ global variable.; Это правильный заказ? Или python сначала посмотрит на __metaclass__ в будущем классе, а затем на его базовые классы, а затем на глобальный __metaclass__?

George Mauer
19 сентября 2019 в 14:22
1

Какую версию Python предполагает этот пример кода и имеет ли это значение?

kapad
6 ноября 2019 в 07:04
2

@ thomas-wouters Эта строка metaclasses are inherited верна? В ответе Джеруба говорится, что метаклассы не унаследованы. Также, что произойдет, если класс является подклассом нескольких классов, и каждый из них имеет свой собственный __metaclass__ (или два или более родительских класса определяют __metaclass__).

chepner
9 января 2020 в 21:14
3

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

Vishesh Mangla
7 июля 2020 в 16:19
0

Ну может кто-нибудь также объяснить, что делает регистровый тег? Я использую класс X (ABC): у меня много времени на создание интерфейсов, но что именно регистр позволит мне сделать? В документации буквально одна строчка написана о регистре.

Reinderien
17 декабря 2020 в 18:36
0

Что говорит @BlackShift. Здесь можно было бы использовать большое жирное предупреждение вверху о том, что весь ответ устарел, поскольку он полагается на Python 2, который сам по себе является устаревшим.

avatar
MuhammadAliDEV
6 февраля 2022 в 13:10
1

Что такое метапрограммирование?

Вкратце можно сказать, что метапрограммирование — это код, который манипулирует кодом. Python поддерживает форму метапрограммирования для классов, называемую метаклассами.

Когда использовать:

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

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

Схема для новичков: Metaclass

Фабрика классов:

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

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

1- ** new():** Это метод, который вызывается перед init(). Он создает объект и возвращает его. Мы можем переопределить этот метод, чтобы управлять созданием объектов.

2- init(): Этот метод просто инициализирует созданный объект, переданный в качестве параметра.

Способы определения метаклассов:

1- Способ 1:

class MyMeta1(type):
  def __new__(cls, name, bases, dict):
    pass

2- Способ 2:

class MyMeta2(type):
  def __init__(self, name, bases, dict):
    pass
avatar
Delta
3 октября 2021 в 16:06
0

посмотрите это:

Python 3.10.0rc2 (tags/v3.10.0rc2:839d789, Sep  7 2021, 18:51:45) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class Object:
...     pass
... 
>>> class Meta(type):
...     test = 'Worked!!!'
...     def __repr__(self):
...             return 'This is "Meta" metaclass'
... 
>>> class ObjectWithMetaClass(metaclass=Meta):
...     pass
... 
>>> Object or type(Object())
<class '__main__.Object'>
>>> ObjectWithMetaClass or type(ObjectWithMetaClass())
This is "Meta" metaclass
>>> Object.test
AttributeError: ...
>>> ObjectWithMetaClass.test
'Worked!!!'
>>> type(Object)
<class 'type'>
>>> type(ObjectWithMetaClass)
<class '__main__.Meta'>
>>> type(type(ObjectWithMetaClass))
<class 'type'>
>>> Object.__bases__
(<class 'object'> )
>>> ObjectWithMetaClass.__bases__
(<class 'object'> )
>>> type(ObjectWithMetaClass).__bases__
(<class 'type'> )
>>> Object.__mro__
(<class '__main__.Object'>  <class 'object'>)
>>> ObjectWithMetaClass.__mro__
(This is "Meta" metaclass, <class 'object'>)
>>> 

Другими словами, когда объект не был создан (тип объекта), мы ищем MetaClass.

avatar
e-satis
20 июля 2021 в 18:45
7367

Классы как объекты

Прежде чем разбираться в метаклассах, нужно освоить мастер-классы по Python. И Python имеет очень своеобразное представление о том, что такое классы, заимствованное из языка Smalltalk.

В большинстве языков классы - это просто фрагменты кода, описывающие, как создать объект. То же самое и в Python:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Но классы - это не только в Python. Классы тоже являются объектами.

Да, объекты.

Как только вы используете ключевое слово class, Python выполняет его и создает объект . Инструкция

>>> class ObjectCreator(object):
...       pass
...

создает в памяти объект с именем ObjectCreator.

Этот объект (класс) сам может создавать объекты (экземпляры), и поэтому это класс .

Но все же это объект, а значит:

  • вы можете присвоить его переменной
  • вы можете скопировать
  • вы можете добавить к нему атрибуты
  • вы можете передать его как параметр функции

например:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Динамическое создание классов

Поскольку классы являются объектами, вы можете создавать их на лету, как любой объект.

Во-первых, вы можете создать класс в функции, используя class:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Но это не так динамично, поскольку вам все равно придется писать весь класс самостоятельно.

Поскольку классы являются объектами, они должны быть чем-то сгенерированы.

Когда вы используете ключевое слово class, Python создает этот объект автоматически. Но, как с большинством вещей в Python это позволяет делать это вручную.

Помните функцию type? Старая добрая функция, которая позволяет узнать, что введите объект:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Ну, type имеет совершенно другую способность, он также может создавать классы на лету. type может принимать описание класса как параметры, и вернуть класс.

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

type работает следующим образом:

type(name, bases, attrs)

Где:

  • name : имя класса
  • bases : кортеж родительского класса (для наследования может быть пустым)
  • attrs : словарь, содержащий имена и значения атрибутов

например:

>>> class MyShinyClass(object):
...       pass

можно создать вручную следующим образом:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

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

type принимает словарь для определения атрибутов класса. Итак:

>>> class Foo(object):
...       bar = True

Можно перевести на:

>>> Foo = type('Foo', (), {'bar':True})

И используется как обычный класс:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

И, конечно, вы можете наследовать от него, поэтому:

>>>   class FooChild(Foo):
...         pass

будет:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

В конце концов, вы захотите добавить методы в свой класс. Просто определите функцию с соответствующей подписью и назначьте ее как атрибут.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

И вы можете добавить еще больше методов после динамического создания класса, точно так же, как добавление методов к обычно создаваемому объекту класса.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

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

Это то, что Python делает, когда вы используете ключевое слово class, и делает это с помощью метакласса.

Что такое метаклассы (наконец)

Метаклассы - это «материал», который создает классы.

Вы определяете классы для создания объектов, верно?

Но мы узнали, что классы Python являются объектами.

Что ж, метаклассы создают эти объекты. Это классы классов, вы можете представить их так:

MyClass = MetaClass()
my_object = MyClass()

Вы видели, что type позволяет делать что-то вроде этого:

MyClass = type('MyClass', (), {})

Это потому, что функция type на самом деле является метаклассом. type - это метакласс, который Python использует для создания всех классов за кулисами.

Теперь вы задаетесь вопросом, «какого черта это написано строчными буквами, а не Type

Ну, я думаю, это вопрос согласованности с str, классом, который создает строковые объекты, а int класс, создающий целочисленные объекты. type - это просто класс, который создает объекты класса.

Вы увидите это, проверив атрибут __class__.

Все, я имею в виду все, является объектом в Python. Сюда входят целые числа, строки, функции и классы. Все они объекты. И все они был создан из класса:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Теперь, что такое __class__ у любого __class__?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Итак, метакласс - это просто материал, который создает объекты класса.

Если хотите, можете называть это «фабрикой классов».

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

Атрибут __metaclass__

В Python 2 вы можете добавить атрибут __metaclass__ при написании класса (синтаксис Python 3 см. В следующем разделе):

class Foo(object):
    __metaclass__ = something...
    [...]

Если вы это сделаете, Python будет использовать метакласс для создания класса Foo.

Осторожно, это сложно.

Сначала вы пишете class Foo(object), но объект класса Foo не создается в памяти пока нет.

Python будет искать __metaclass__ в определении класса. Если найдет, он будет использовать его для создания класса объекта Foo. Если этого не произойдет, он будет использовать type для создания класса.

Прочтите это несколько раз.

Когда вы это сделаете:

class Foo(Bar):
    pass

Python делает следующее:

Есть ли атрибут __metaclass__ в Foo?

Если да, создайте в памяти объект класса (я сказал, что объект класса, оставайтесь со мной здесь) с именем Foo, используя то, что находится в __metaclass__.

Если Python не может найти __metaclass__, он будет искать __metaclass__ на уровне МОДУЛЯ и пытаться сделать то же самое (но только для классов, которые ничего не наследуют, в основном классы старого стиля) .

Затем, если он не может найти никакого __metaclass__, он будет использовать собственный метакласс Bar (первого родителя) (который может быть type по умолчанию) для создания объекта класса.

>

Обратите внимание, что атрибут __metaclass__ не будет унаследован, а будет унаследован метакласс родительского элемента (Bar.__class__). Если Bar использовал атрибут __metaclass__, создавший Bar с type() (а не type.__new__()), подклассы не унаследуют это поведение.

Теперь большой вопрос: что можно вставить в __metaclass__?

Ответ - то, что может создать класс.

А что можно создать класс? type или что-либо, что его подклассифицирует или использует.

Метаклассы в Python 3

Синтаксис для установки метакласса был изменен в Python 3:

class Foo(object, metaclass=something):
    ...

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

Однако поведение метаклассов остается в основном тем же.

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

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

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

Пользовательские метаклассы

Основная цель метакласса - автоматически изменять класс, при его создании.

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

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

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

К счастью, __metaclass__ может быть любым вызываемым, это не обязательно формальный класс (я знаю, что-то, в названии которого есть слово "класс", не обязательно класс, пойди ... но это полезно).

Итак, мы начнем с простого примера, используя функцию.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """
    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

Давайте проверим:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Теперь давайте сделаем то же самое, но с использованием реального класса для метакласса:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

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

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

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

Но это неправильное ООП. Мы вызываем type напрямую, и мы не переопределяем и не вызываем родительский __new__. Давайте сделаем это вместо этого:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

Мы можем сделать его еще чище, используя super, который упростит наследование (потому что да, у вас могут быть метаклассы, наследование от метаклассов, наследование от типа):

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

О, и в Python 3, если вы выполняете этот вызов с ключевыми аргументами, например:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

Для использования в метаклассе он переводится так:

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

Вот и все. О метаклассах больше нет ничего.

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

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

  • перехватить создание класса
  • изменить класс
  • вернуть измененный класс

Зачем использовать классы метаклассов вместо функций?

Поскольку __metaclass__ может принимать любой вызываемый объект, зачем вам использовать класс поскольку это явно сложнее?

Для этого есть несколько причин:

  • Намерение ясно. Когда вы читаете UpperAttrMetaclass(type), вы знаете что будет дальше
  • Вы можете использовать ООП. Метакласс может наследовать от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
  • Подклассы класса будут экземплярами его метакласса, если вы указали класс метакласса, но не с помощью функции метакласса.
  • Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как приведенный выше пример. Обычно это для чего-то сложного. Возможность создавать несколько методов и группировать их в один класс очень полезна для облегчения чтения кода.
  • Вы можете подключить __new__, __init__ и __call__. Что позволит вам делать разные вещи, даже если обычно вы можете делать все это в __new__, некоторым людям просто удобнее использовать __init__.
  • Это называется метаклассами, черт возьми! Это должно что-то значить!

Зачем использовать метаклассы?

А теперь большой вопрос. Зачем вам использовать какую-то непонятную функцию, подверженную ошибкам?

Ну, обычно нет:

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

Гуру Python Тим Петерс

Основным вариантом использования метакласса является создание API. Типичным примером этого является Django ORM. Это позволяет вам определить что-то вроде этого:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Но если вы сделаете это:

person = Person(name='bob', age='35')
print(person.age)

Он не вернет объект IntegerField. Он вернет int и даже может взять его прямо из базы данных.

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

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

Последнее слово

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

На самом деле классы сами по себе являются экземплярами. Метаклассов.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Все является объектом в Python, и все они являются экземплярами классов. или экземпляры метаклассов.

За исключением type.

type на самом деле является отдельным метаклассом. Это не то, что вы могли бы воспроизвести на чистом Python, и это делается путем небольшого обмана в реализации уровень.

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

В 99% случаев, когда вам нужно изменить класс, вам лучше использовать их.

Но в 98% случаев вам вообще не нужно менять класс.

Max Goodridge
12 апреля 2017 в 13:18
42

Похоже, что в Django models.Model он не использует __metaclass__, а скорее class Model(metaclass=ModelBase): для ссылки на класс ModelBase, который затем выполняет вышеупомянутую магию метакласса. Отличный пост! Вот исходный код Django: github.com/django/django/blob/master/django/db/models/…

Spybdai
20 апреля 2017 в 03:24
0

что, если указать разные метаклассы как в базовом, так и в производном классе?

petrux
25 апреля 2017 в 21:32
22

<< Будьте осторожны: атрибут __metaclass__ не будет унаследован, а метакласс родительского элемента (Bar.__class__) будет унаследован. Если Bar использовал атрибут __metaclass__, который создал Bar с type() (а не type.__new__()), подклассы не унаследуют это поведение. >> - Не могли бы вы / кто-нибудь, пожалуйста, объясните немного подробнее этот отрывок ?

TBBle
13 июня 2017 в 13:22
21

@MaxGoodridge Это синтаксис Python 3 для метаклассов. См. Модель данных Python 3.6 VS Модель данных Python 2.7

Deep
25 июня 2017 в 14:43
0

повторяя вопрос @petrux. Я потерялся при заявлении: Be careful here that the __metaclass__ attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a __metaclass__ attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior. Кто-нибудь, пожалуйста, объясните это немного глубже? Был бы очень признателен за помощь здесь.

pppery
3 августа 2017 в 14:42
0

«type на самом деле является отдельным метаклассом. Это не то, что вы могли бы воспроизвести в чистом Python, и это делается путем небольшого мошенничества на уровне реализации». неправда. __class__ доступен для записи в Python, поэтому можно создать подкласс типа с настраиваемым метаклассом, а затем установить его класс на себя.

Philip Stark
16 августа 2017 в 09:17
1

@Deep: фактический метакласс класса указан в .__class__, тогда как .__metaclass__ указывает, какой вызываемый объект следует использовать для изменения класса во время создания. Если, например, .__metaclass__ содержит функцию foo_bar(), которая использует type(x,y,z) для изменения класса, тогда у вас будет. __metaclass__ = foo_bar, который не будет унаследован, но .__class__ будет type, потому что он был использован для создания нового измененного класса. Прочтите это несколько раз. Я на 99% уверен, что не ошибся;)

Mr_and_Mrs_D
29 сентября 2017 в 10:47
6

Now you wonder why the heck is it written in lowercase, and not Type? - ну, потому что он реализован на C - по той же причине defaultdict имеет нижний регистр, а OrderedDict (в python 2) - нормальный CamelCase

Brōtsyorfuzthrāx
8 ноября 2017 в 08:59
21

Это ответ сообщества вики (поэтому те, кто прокомментировал исправления / улучшения, могут подумать о редактировании своих комментариев в ответе, если они уверены, что они верны).

René Nyffenegger
3 апреля 2018 в 12:40
0

Если метакласс является объектом, разве у этого объекта нет метакласса?

Nearoo
4 сентября 2018 в 14:10
1

@ RenéNyffenegger Да, у вас может быть метакласс метакласса метакласса

polarise
18 апреля 2019 в 14:32
1

Динамическое создание методов немного сложнее, чем показано в тексте. Хотя здесь он работает, он не учитывает случаи, когда мы ссылаемся на объект self. Например, метод def foo(self, *args, **kwargs): print(self, args, kwargs) будет делать разные вещи, если он создается динамически или как часть определения класса. Правильный способ создать его динамически - выполнить тело в пространстве имен, а затем присоединить его (см. Python Essential Reference 4th Edition, David Beazly p.138 ).

skytree
29 мая 2019 в 11:31
0

Почему type(...) на самом деле не является ООП по сравнению с type.__new__(...)?

Karuhanga
10 июля 2019 в 08:14
0

Subclasses of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function. Почему это так?

artu-hnrq
30 апреля 2020 в 08:39
0

Каруханга, metaclass-function ссылался на любой метод, который манипулирует объявлением класса, вызывая type.__new__(x, y, z) напрямую таким образом, что для только что созданного класса не установлен конкретный __metaclass__ (т.е. его метакласс будет type). Таким образом, подклассы этого класса также по умолчанию будут иметь тот же класс mataclass (то есть type)

3rdi
21 мая 2020 в 09:14
0

Могу ли я узнать, если все является объектом в Python ... тип также является объектом. Итак, тип также создается с использованием некоторого родительского класса. Если мы попытаемся понять это, где именно заканчивается это родительское дерево? В чем его корень? и как это все отображается на карте. Короче говоря, я спрашиваю, как, наверное, создать что-то вроде python с нуля.

chepner
11 июня 2020 в 17:52
0

@ 3rdi Это не совсем правильное дерево, так как истинного корня не существует. Нет типа без метакласса; метакласс type - type. Именно этот цикл в графе не позволяет ему быть деревом, а также предотвращает определение type в самом Python. Это должно быть предусмотрено реализацией.

user3225309
18 мая 2021 в 04:33
1

Замечательное и очень краткое объяснение. Поздравляю автора этого ответа.

Nirvana
24 июня 2021 в 01:31
0

Это заставило меня понять, что Python - это всего лишь язык на основе прототипов под прикрытием языка на основе классов.

avatar
Manukumar
17 июля 2021 в 05:18
-2

Метаклассы - это не что иное, как простой внутренний класс вашего класса модели, например: В Django или python

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    class Meta:
        abstract = True

Здесь, если abstract = True, эта модель будет абстрактным базовым классом. Метакласс изменит поведение вашего базового класса.

avatar
Emma Brown
12 июля 2021 в 22:51
3

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

avatar
DrosnickX
21 апреля 2021 в 18:41
3

В Python метакласс - это подкласс подкласса, который определяет поведение подкласса. Класс - это экземпляр другого метакласса. В Python класс определяет, как будет вести себя экземпляр класса.

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

avatar
Technical A.D.
29 марта 2020 в 09:25
-10

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

swastik
1 ноября 2020 в 18:39
1

Пожалуйста, добавьте релевантную информацию ... ваши комментарии сбивают с толку

avatar
Lars
3 марта 2020 в 10:06
10

Обратите внимание, что в python 3.6 был введен новый dunder-метод __init_subclass__(cls, **kwargs) для замены множества распространенных вариантов использования метаклассов. Is вызывается при создании подкласса определяющего класса. См. документы python.

avatar
Swati Srivastava
20 января 2020 в 06:59
6

Класс в Python - это объект, и, как и любой другой объект, он является экземпляром «чего-то». Это «что-то» называется метаклассом. Этот метакласс - это особый тип класса, который создает объекты других классов. Следовательно, метакласс отвечает за создание новых классов. Это позволяет программисту настроить способ создания классов.

Для создания метакласса обычно выполняется переопределение методов new () и init (). new () можно переопределить, чтобы изменить способ создания объектов, а init () можно переопределить, чтобы изменить способ инициализации объекта. Метакласс можно создать несколькими способами. Один из способов - использовать функцию type (). Функция type () при вызове с 3 параметрами создает метакласс. Параметры: -

  1. Имя класса
  2. Кортеж, имеющий базовые классы, унаследованные классом
  3. Словарь, содержащий все методы класса и переменные класса

Другой способ создания метакласса состоит из ключевого слова «метакласс». Определите метакласс как простой класс. В параметрах унаследованного класса передайте metaclass = metaclass_name

Метакласс можно специально использовать в следующих ситуациях: -

  1. когда конкретный эффект должен быть применен ко всем подклассам
  2. Требуется автоматическая смена класса (при создании)
  3. Разработчиками API
avatar
Carson
20 декабря 2019 в 11:03
8

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

  • Вы можете использовать metaclass, чтобы изменить функцию его экземпляра (класса).
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

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

avatar
Venu Gopal Tewari
9 июля 2019 в 05:37
8

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

verisimilitude
13 июля 2019 в 17:41
2

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

Venu Gopal Tewari
15 июля 2019 в 06:02
1

@verisimilitude Я тоже изучаю, можете ли вы помочь мне улучшить этот ответ, предоставив несколько практических примеров из своего опыта ??

avatar
Andy Fedoroff
15 сентября 2018 в 12:41
18

В дополнение к опубликованным ответам я могу сказать, что metaclass определяет поведение класса. Итак, вы можете явно установить свой метакласс. Когда Python получает ключевое слово class, он начинает поиск metaclass. Если он не найден - для создания объекта класса используется тип метакласса по умолчанию. Используя атрибут __metaclass__, вы можете установить metaclass своего класса:

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

Результат будет примерно таким:

class 'type'

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

Для этого ваш класс типа metaclass по умолчанию должен быть унаследован, поскольку это основной metaclass:

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

Вывод будет:

class '__main__.MyMetaClass'
class 'type'
avatar
binbjz
12 января 2018 в 09:16
27

Функция type () может возвращать тип объекта или создавать новый тип,

например, мы можем создать класс Hi с помощью функции type (), и нет необходимости использовать этот способ с классом Hi (object):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

Помимо использования type () для динамического создания классов, вы можете управлять поведением создания класса и использовать метакласс.

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

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Magic вступит в силу, когда мы передадим аргументы ключевого слова в метаклассе, это указывает интерпретатору Python создать CustomList через ListMetaclass. new (), на этом этапе мы можем изменить определение класса, например, и добавить новый метод, а затем вернуть измененное определение.

avatar
Xingzhou Liu
13 июля 2017 в 07:58
33

Классы Python сами являются объектами - как, например, - своего метакласса.

Метакласс по умолчанию, который применяется, когда вы определяете классы как:

class foo:
    ...

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

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

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,...  object), {"an_attr":12, "bar": <function>  "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

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

avatar
noɥʇʎԀʎzɐɹƆ
27 декабря 2016 в 02:21
52

Версия tl; dr

Функция type(obj) определяет тип объекта.

type() класса - это его метакласс .

Чтобы использовать метакласс:

class Foo(object):
    __metaclass__ = MyMetaClass

type - отдельный метакласс. Класс класса - это метакласс - тело класса - это аргументы, переданные метаклассу, который используется для создания класса.

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

avatar
Michael Ekoka
13 октября 2016 в 09:21
78

Роль метода __call__() метакласса при создании экземпляра класса

Если вы занимались программированием на Python более нескольких месяцев, вы в конечном итоге наткнетесь на код, который выглядит следующим образом:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

Последнее возможно, если вы реализуете магический метод __call__() в классе.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

Метод __call__() вызывается, когда экземпляр класса используется как вызываемый. Но, как мы видели из предыдущих ответов, сам класс является экземпляром метакласса, поэтому, когда мы используем класс как вызываемый (т.е. когда мы создаем его экземпляр), мы фактически вызываем метод __call__() его метакласса . На этом этапе большинство программистов Python немного сбиты с толку, потому что им сказали, что при создании такого экземпляра instance = SomeClass() вы вызываете его метод __init__(). Некоторые, кто копал немного глубже, знают, что до __init__() было __new__(). Что ж, сегодня открывается еще один пласт истины, до __new__() есть метакласс __call__().

Давайте изучим цепочку вызовов методов с точки зрения создания экземпляра класса.

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

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

Это класс, который использует этот метакласс

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

А теперь давайте создадим экземпляр Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

Обратите внимание, что приведенный выше код на самом деле не делает ничего, кроме регистрации задач. Каждый метод делегирует фактическую работу своей родительской реализации, таким образом сохраняя поведение по умолчанию. Поскольку type является родительским классом Meta_1 (type является родительским метаклассом по умолчанию) и с учетом упорядоченной последовательности вывода выше, мы теперь имеем представление о том, какой будет псевдореализация type.__call__():

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

Мы видим, что метод __call__() метакласса вызывается первым. Затем он делегирует создание экземпляра методу класса __new__() и инициализацию экземпляра __init__(). Он также возвращает экземпляр.

Из вышесказанного следует, что метаклассу __call__() также предоставляется возможность решить, будет ли в конечном итоге сделан вызов Class_1.__new__() или Class_1.__init__(). В ходе выполнения он может фактически вернуть объект, который не был затронут ни одним из этих методов. Возьмем, например, такой подход к шаблону singleton:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

Давайте посмотрим, что происходит при многократных попытках создать объект типа Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True
Rich Lysakowski PhD
26 ноября 2019 в 03:51
1

Это хорошее дополнение к ранее одобренному «принятому ответу». Он предоставляет примеры для программистов среднего уровня.

avatar
Mushahid Khan
9 августа 2016 в 18:49
50

type на самом деле является metaclass - классом, который создает другие классы. Большинство metaclass являются подклассами type. metaclass получает класс new в качестве своего первого аргумента и предоставляет доступ к объекту класса с подробностями, как указано ниже:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

Обратите внимание, что экземпляр класса не создавался ни разу; простой акт создания класса инициировал выполнение metaclass.

avatar
Ethan Furman
1 марта 2016 в 19:48
94

Обновление Python 3

В метаклассе есть (на данный момент) два ключевых метода:

  • __prepare__ и
  • __new__

__prepare__ позволяет указать настраиваемое сопоставление (например, OrderedDict), которое будет использоваться в качестве пространства имен при создании класса. Вы должны вернуть экземпляр любого выбранного вами пространства имен. Если вы не реализуете __prepare__, будет использоваться обычный dict.

__new__ отвечает за фактическое создание / модификацию последнего класса.

Простой метакласс, не требующий дополнительных действий, хотел бы:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Простой пример:

Предположим, вы хотите, чтобы для ваших атрибутов выполнялся простой код проверки - например, он всегда должен быть int или str. Без метакласса ваш класс выглядел бы примерно так:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

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

Простой метакласс может решить эту проблему:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

Вот как будет выглядеть метакласс (без использования __prepare__, поскольку он не нужен):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Пример выполнения:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

производит:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

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

Класс ValidateType для справки:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value
Lars
3 марта 2020 в 09:56
1

Обратите внимание, что начиная с python 3.6 вы можете использовать __set_name__(cls, name) в дескрипторе (ValidateType), чтобы установить имя в дескрипторе (self.name и в этом случае также self.attr). Это было добавлено, чтобы не погружаться в метаклассы для этого конкретного общего варианта использования (см. PEP 487).

avatar
Craig
25 января 2016 в 20:08
65

Метакласс - это класс, который сообщает, как (какой-то) другой класс должен быть создан.

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

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()
avatar
Aaron Hall
10 августа 2015 в 23:28
127

Что такое метаклассы? Для чего вы их используете?

TL; DR: метакласс создает и определяет поведение для класса точно так же, как класс создает и определяет поведение для экземпляра.

Псевдокод:

>>> Class(...)
instance

Вышеупомянутое должно показаться вам знакомым. Ну откуда взялось Class? Это экземпляр метакласса (также псевдокода):

>>> Metaclass(...)
Class

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

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Другими словами

  • Класс относится к экземпляру, как метакласс относится к классу.

    Когда мы создаем экземпляр объекта, мы получаем экземпляр:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

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

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Другими словами, класс является экземпляром метакласса:

    >>> isinstance(object, type)
    True
    
  • Иными словами, метакласс - это класс класса.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

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

Так же, как мы можем использовать определения классов для изменения поведения экземпляров настраиваемых объектов, мы можем использовать определение класса метакласса для изменения поведения объекта класса.

Для чего их можно использовать? Из документов:

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

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

Вы используете метакласс каждый раз, когда создаете класс:

Когда вы пишете определение класса, например, как это,

class Foo(object): 
    'demo'

Вы создаете экземпляр объекта класса.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

Это то же самое, что функциональный вызов type с соответствующими аргументами и присвоение результата переменной с таким именем:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Обратите внимание, что некоторые вещи автоматически добавляются в __dict__, то есть в пространство имен:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>  
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>  '__doc__': 'demo'})

Метакласс созданного нами объекта в обоих случаях - type.

(Примечание о содержимом класса __dict__: __module__ здесь, потому что классы должны знать, где они определены, а __dict__ и __weakref__ присутствуют, потому что мы не определяем __slots__ - если мы определим __slots__, мы сэкономим немного места в экземплярах, так как мы можем запретить __dict__ и __weakref__, исключив их. Например:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... но я отвлекся.)

Мы можем расширить type, как любое другое определение класса:

Вот __repr__ классов по умолчанию:

>>> Foo
<class '__main__.Foo'>

Одна из наиболее ценных вещей, которые мы можем сделать по умолчанию при написании объекта Python, - это предоставить ему хороший __repr__. Когда мы вызываем help(repr), мы узнаем, что есть хороший тест для __repr__, который также требует проверки на равенство - obj == eval(repr(obj)). Следующая простая реализация __repr__ и __eq__ для экземпляров класса нашего класса типа предоставляет нам демонстрацию, которая может улучшить __repr__ классов по умолчанию:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Итак, теперь, когда мы создаем объект с помощью этого метакласса, __repr__, отображаемый в командной строке, обеспечивает гораздо менее уродливый вид, чем значение по умолчанию:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

С красивым __repr__, определенным для экземпляра класса, у нас есть более сильные возможности для отладки нашего кода. Однако дальнейшая проверка с помощью eval(repr(Class)) маловероятна (поскольку функции было бы довольно невозможно вычислить по умолчанию с __repr__).

Ожидаемое использование: __prepare__ пространство имен

Если, например, мы хотим знать, в каком порядке создаются методы класса, мы могли бы предоставить упорядоченный dict в качестве пространства имен класса. Мы бы сделали это с помощью __prepare__, который возвращает dict пространства имен для класса, если он реализован в Python 3:

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

И использование:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

И теперь у нас есть запись о порядке, в котором эти методы (и другие атрибуты класса) были созданы:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Обратите внимание, этот пример был адаптирован из документации - новое перечисление в стандартной библиотеке делает это.

Итак, мы создали экземпляр метакласса, создав класс. Мы также можем обращаться с метаклассом, как с любым другим классом. Он имеет порядок разрешения метода:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>  <class '__main__.Type'>  <class 'type'>  <class 'object'>)

И он имеет приблизительно правильный repr (который мы больше не можем оценивать, если не найдем способ представления наших функций.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>  'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>  'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>  '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>  '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>  'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
avatar
kindall
21 июня 2011 в 16:30
192

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

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

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

Michael Gundlach
16 апреля 2019 в 17:59
0

Спасибо за пример. Почему вам это показалось проще, чем наследование от MyBase, у которого __init__(self) указано type(self)._order = MyBase.counter; MyBase.counter += 1?

kindall
16 апреля 2019 в 18:09
2

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

Michael Gundlach
17 апреля 2019 в 21:58
0

Хорошо, да. Спасибо. Мой код сбрасывал атрибут MyType при каждом создании экземпляра и никогда не устанавливал атрибут, если экземпляр MyType никогда не создавался. Ой. (И свойство класса также может работать, но, в отличие от метакласса, оно не предлагает очевидного места для хранения счетчика.)

mike rodent
22 октября 2019 в 00:09
1

Это очень интересный пример, не в последнюю очередь потому, что можно действительно понять, почему с этим может понадобиться метакласс, чтобы предоставить решение конкретной трудности. OTOH Я изо всех сил пытаюсь убедиться, что кому-то действительно нужно создавать экземпляры объектов в том порядке, в котором были определены их классы: я думаю, нам просто нужно поверить вам на слово :).

kindall
5 мая 2021 в 20:57
0

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

avatar
Antti Rasinen
19 сентября 2008 в 06:45
178

Одно из применений метаклассов - автоматическое добавление новых свойств и методов к экземпляру.

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

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

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

trixn
27 января 2017 в 23:24
8

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

avatar
Matthias Kestenholz
19 сентября 2008 в 06:32
138

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

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (в архиве https://web.archive.org/ web / 20080206005253 / http: //www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html)

Вкратце: класс - это план для создания экземпляра, метакласс - это план для создания класса. Легко видеть, что в Python классы также должны быть первоклассными объектами, чтобы обеспечить такое поведение.

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

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

avatar
Jerub
19 сентября 2008 в 06:26
440

Обратите внимание, этот ответ предназначен для Python 2.x, поскольку он был написан в 2008 году, метаклассы немного отличаются в 3.x.

Метаклассы - секретный соус, который заставляет «класс» работать. Метакласс по умолчанию для нового объекта стиля называется «тип».

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Метаклассы принимают 3 аргумента. ' имя ', ' базы ' и ' dict '

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

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Давайте определим метакласс, который продемонстрирует, как его называет class: .

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>  None, <type 'int'>  1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

А теперь пример, который на самом деле что-то означает, это автоматически сделает переменные в списке "атрибутов" установленными для класса и установят значение None.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Обратите внимание, что магическое поведение, которое Initialised получает за счет наличия метакласса init_attributes, не передается в подкласс Initialised .

Вот еще более конкретный пример, показывающий, как можно создать подкласс «тип», чтобы создать метакласс, который выполняет действие при создании класса. Это довольно сложно:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

class Foo(object):
    __metaclass__ = MetaSingleton

a = Foo()
b = Foo()
assert a is b