Вопрос

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

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

Решение

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

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

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

Когда class выполняется, Python сначала выполняет тело class оператор как обычный блок кода.Полученное пространство имен (диктант) содержит атрибуты будущего класса.Метакласс определяется путем просмотра базовых классов будущего класса (метаклассы наследуются), __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__

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

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

Прежде чем разбираться в метаклассах, вам необходимо освоить классы 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 of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

например.:

>>> 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 Metaclass Python использует для создания всех классов за кулисами.

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

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

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

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

>>> 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 Это встроенный Metaclass Python, но, конечно, вы можете создать свой собственный MetaClass.

А __metaclass__ атрибут

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

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

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

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

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

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

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

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

class Foo(Bar):
    pass

Питон делает следующее:

Есть ли __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 справляется с этим.

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

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

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

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

Таким образом, все классы этого модуля будут созданы с использованием этого MetaClass, и мы просто должны сообщить 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_attr):
    """
      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_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

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

__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'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: '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_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

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

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

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

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

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

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

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

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

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

В метаклассе это переводится как:

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

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

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

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

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

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

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

Есть несколько причин сделать это:

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

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

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

Ну, обычно вы этого не делаете:

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

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

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

Это позволяет вам определить что-то вроде этого:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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'

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

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

Обратите внимание, что волшебное поведение, которое «Инициализировано», достигается за счет наличия метакласса init_attributes не передается в подкласс Initalized.

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

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

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

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

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

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

Другие объяснили, как работают метаклассы и как они вписываются в систему типов 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 который записывает порядок, в котором были определены классы.

Я думаю, что введение 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 также должны быть первоклассными объектами, чтобы обеспечить такое поведение.

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

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

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

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

Псевдокод:

>>> 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__ для экземпляров нашего типа class предоставляет нам демонстрацию, которая может улучшить стандартный __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>})

Обновление 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

Роль метакласса' __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

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

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

#!/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()

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

>>> 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.

Версия tl;dr

А type(obj) функция возвращает вам тип объекта.

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

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

class Foo(object):
    __metaclass__ = MyMetaClass

Классы 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

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

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

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

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']

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

В дополнение к опубликованным ответам могу сказать, что 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'
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top