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




метаклассы python это (12)

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

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

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

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

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


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

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

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

Однако во время выполнения объекты Person заполняются всякими полезными методами. Посмотрите source какой-то удивительной метаклассификации.


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


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

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

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

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

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

Но классы больше, чем в Python. Классы тоже объекты.

Да, объекты.

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

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

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

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

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

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

>>> 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 делает следующее:

В Foo есть атрибут __metaclass__ ?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Обычно вы делаете это для 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_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 , все аргументы имеют обычные имена. Итак, настоящий метакласс будет выглядеть так:

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)

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

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

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

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

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

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

Этому есть несколько причин:

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

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

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

Ну, обычно у вас нет:

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

Python Guru Тим Петерс

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

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

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

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

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

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

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

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

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

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

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

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

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

Но в 98% случаев вам вообще не нужны изменения класса.


Я думаю, что введение 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%.


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

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

  • __prepare__ , а также
  • __new__

__prepare__позволяет вам OrderedDictсоздавать настраиваемое сопоставление (например, an ), которое будет использоваться в качестве пространства имен во время создания класса. Вы должны вернуть экземпляр любого пространства имен, которое вы выберете. Если вы не реализуете __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)

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

Предположим, вы хотите, чтобы какой-то простой код проверки выполнялся по вашим атрибутам - например, он всегда должен быть a intили a 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

Версия tl; dr

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

Класс type()представляет собой его метакласс .

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

class Foo(object):
    __metaclass__ = MyMetaClass

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.


В дополнение к опубликованным ответам я могу сказать, что a 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'

Функция 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

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

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

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. new (), на этом этапе мы можем, например, изменить определение класса и добавить новый метод, а затем вернуть пересмотренное определение.


Роль метода метакласса __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__()будет вызван. В ходе его выполнения он мог бы фактически вернуть объект, который не был затронут ни одним из этих методов. Возьмем, к примеру, такой подход к одноэлементному шаблону:

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

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

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

class foo:
    ...

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

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





python-datamodel