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

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

вопрос задан 19.09.2008
e-satis
342132 репутация

16 ответов


  • 5847 рейтинг

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

    Прежде чем разбираться в метаклассах, вам нужно освоить классы на 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
    
    >>> def echo(o):
    ...       print(o)
    ...
    >>> echo(ObjectCreator) # you can pass a class as a parameter
    
    >>> 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
    
    >>> print(MyClass()) # you can create an object from this class
    <__main__.Foo object at 0x89c6d4c>
    

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

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

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

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

    >>> print(type(1))
    
    >>> print(type("1"))
    
    >>> print(type(ObjectCreator))
    
    >>> print(type(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)
    
    >>> 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)
    
    >>> 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)
    
    >>> print(FooChild.bar) # bar is inherited from Foo
    True
    

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

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

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

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

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

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

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

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

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

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

    Ну, именно метаклассы создают эти объекты. Они классы классов, Вы можете изобразить их таким образом:

    MyClass = MetaClass()
    my_object = MyClass()
    

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

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

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

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

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

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

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

    >>> age = 35
    >>> age.__class__
    
    >>> name = 'bob'
    >>> name.__class__
    
    >>> def foo(): pass
    >>> foo.__class__
    
    >>> class Bar(object): pass
    >>> b = Bar()
    >>> b.__class__
    
    

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

    >>> age.__class__.__class__
    
    >>> name.__class__.__class__
    
    >>> foo.__class__.__class__
    
    >>> b.__class__.__class__
    
    

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

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

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

    атрибут __metaclass__

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

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

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

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

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

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

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

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

    class Foo(Bar):
        pass
    

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

    Имеется ли атрибут __metaclass__ в Foo?

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

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

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

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

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

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

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

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

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

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

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

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

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

    Основное назначение метакласса - автоматическое изменение класса. когда это будет создано.

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

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

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

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

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

    # 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)
    

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

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

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

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

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

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

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

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

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

    Теперь большой вопрос. Зачем вам использовать некоторые скрытые ошибки?

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

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

    Питон Гуру Тим Питерс

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

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

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

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

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

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

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

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

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

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

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

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

    ответ дан e-satis, с репутацией 342132, 5.07.2011
  • 2164 рейтинг

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

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

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

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

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

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

    def make_hook(f):
        """Decorator to turn 'foo' method into '__foo__'"""
        f.is_hook = 1
        return f
    
    class MyType(type):
        def __new__(mcls, name, bases, attrs):
    
            if name.startswith('None'):
                return None
    
            # Go over attributes and see if they should be renamed.
            newattrs = {}
            for attrname, attrvalue in attrs.iteritems():
                if getattr(attrvalue, 'is_hook', 0):
                    newattrs['__%s__' % attrname] = attrvalue
                else:
                    newattrs[attrname] = attrvalue
    
            return super(MyType, mcls).__new__(mcls, name, bases, newattrs)
    
        def __init__(self, name, bases, attrs):
            super(MyType, self).__init__(name, bases, attrs)
    
            # classregistry.register(self, self.interfaces)
            print "Would register class %s now." % self
    
        def __add__(self, other):
            class AutoClass(self, other):
                pass
            return AutoClass
            # Alternatively, to autogenerate the classname as well as the class:
            # return type(self.__name__ + other.__name__, (self, other), {})
    
        def unregister(self):
            # classregistry.unregister(self)
            print "Would unregister class %s now." % self
    
    class MyObject:
        __metaclass__ = MyType
    
    
    class NoneSample(MyObject):
        pass
    
    # Will print "NoneType None"
    print type(NoneSample), repr(NoneSample)
    
    class Example(MyObject):
        def __init__(self, value):
            self.value = value
        @make_hook
        def add(self, other):
            return self.__class__(self.value + other.value)
    
    # Will unregister the class
    Example.unregister()
    
    inst = Example(10)
    # Will fail with an AttributeError
    #inst.unregister()
    
    print inst + inst
    class Sibling(MyObject):
        pass
    
    ExampleSibling = Example + Sibling
    # ExampleSibling is now a subclass of both Example and Sibling (with no
    # content of its own) although it will believe it's called 'AutoClass'
    print ExampleSibling
    print ExampleSibling.__mro__
    
    ответ дан Thomas Wouters, с репутацией 87720, 19.09.2008
  • 317 рейтинг

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

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

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

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

    Вот где секрет начинается. Ищите, откуда взято имя, основания и диктат в этом примере определения класса.

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

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

    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 (, None, , 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
    

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

    Вот еще более конкретный пример, показывающий, как можно создать подкласс '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
    
    ответ дан Jerub, с репутацией 31308, 19.09.2008
  • 129 рейтинг

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

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

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

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

    ответ дан Antti Rasinen, с репутацией 6403, 19.09.2008
  • 122 рейтинг

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

    ответ дан kindall, с репутацией 123292, 21.06.2011
  • 91 рейтинг

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

    http: // www. onlamp. ком / PUB / а / Python / 2003/04/17 / метаклассы. html (архив https: // web. архив. орг / веб / 20080206005253 / HTTP: // WWW. onlamp. ком / PUB / а / Python / 2003/04/17 / метаклассы. HTML )

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

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

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

    ответ дан Matthias Kestenholz, с репутацией 2250, 19.09.2008
  • 78 рейтинг

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

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

    Псевдокод:

    >>> Class(...)
    instance
    

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

    >>> Metaclass(...)
    Class
    

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

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

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

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

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

      >>> object()                          # instantiation of class
           # instance
      

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

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

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

      >>> type(object) == type
      True
      >>> object.__class__
      
      

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

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

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

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

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

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

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

    class Foo(object): 
        'demo'
    

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

    >>> 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__': , 
    '__module__': '__main__', '__weakref__': , '__doc__': 'demo'})
    

    Метакласс 3230269583 объекта, который мы создали, в обоих случаях - 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
    
    

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    >>> inspect.getmro(OrderedType)
    (, , , )
    

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

    >>> OrderedMethodsObject
    OrderedType('OrderedMethodsObject', (object,), {'method1': , 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': , 'method2': , '__module__': '__main__', '__weakref__': , '__doc__': None, '__d
    ict__': , 'method4': })
    
    ответ дан Aaron Hall, с репутацией 153756, 10.08.2015
  • 57 рейтинг

    Обновление 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 
        p.weight = '9'
      File "simple_meta.py", line 24, in __set__
        (self.name, self.type, value))
    TypeError: weight must be of type(s)  (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
    
    ответ дан Ethan Furman, с репутацией 34287, 1.03.2016
  • 44 рейтинг

    Роль метода метакласса '__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_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()
    #  singleton returning from Meta_2.__call__(), skipping creation of new instance.
    
    c = Class_2()
    #  singleton returning from Meta_2.__call__(), skipping creation of new instance.
    
    a is b is c # True
    
    ответ дан mike_e, с репутацией 8169, 13.10.2016
  • 42 рейтинг

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

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

    #!/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('', 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()
    
    ответ дан Craig, с репутацией 2437, 24.02.2014
  • 28 рейтинг

    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 : 
    Defining class 
    get_choch :'dairy'
    __module__ :'builtins'
    __qualname__ :'NewClass'
    

    Note:

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

    ответ дан Mushahid Khan, с репутацией 2136, 9.08.2016
  • 24 рейтинг

    TL; Dr версия

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

    type() класса является его метаклассом .

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

    class Foo(object):
        __metaclass__ = MyMetaClass
    
    ответ дан noɥʇʎԀʎzɐɹƆ, с репутацией 2709, 27.12.2016
  • 14 рейтинг

    Классы 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": , "foo": }
    
          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
    

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

    ответ дан Xingzhou Liu, с репутацией 835, 13.07.2017
  • 9 рейтинг

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

    Первоначальный источник: сегментфо. com / a / 1190000011447445

    переведено и исправлено мной.

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

    Если есть какие-то ошибки или какой-либо формат в отношении PEP8, пожалуйста, помогите мне исправить это. Спасибо! ! !

    В начале есть несколько примеров из традиционной китайской культуры (я не китаец, но у меня есть некоторые знания об этом. Если вам это нравится, это хорошо. А если нет, просто проигнорируйте это. Понимание метакласса это самое главное)

    Это краткое введение в Metaclass в Python с некоторыми практическими и полезными примерами. Желаю вам понравится.

    Не пугайтесь такой риторики, как так называемая «особенность», заключающаяся в том, что метакласс не используется 99% программистов на Python. «Потому что каждый человек - естественный пользователь.

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

    предложение 1: один пришел от истины, два пришли от одного, три пришли от двух, все вещи пришли от трех

    предложение 2: кто я? Откуда я пришел? Куда я иду?

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

    один пришел от истины, два пришли от одного, три пришли от двух, все вещи пришли от трех:

    правда типа

    Одним из них является метакласс (метакласс или генератор классов)

    Вторым является класс (класс или генератор экземпляра)

    Три - это экземпляр (пример)

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

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

    # Create a Hello class that has the attributes say_hello ---- Second Origin
    
    class Hello () :
    
         def say_hello ( self ,name= 'world' ) :
    
             print( 'Hello, %s.' % name )
    
    
    
    
    
    # Create an instance hello from the Hello class ---- Two students three
    
    Hello = Hello ()
    
    
    
    # Use hello to call the method say_hello ---- all three things
    
    Hello.say_hello () 
    

    Выходной эффект:

    Hello, world. 
    

    Это стандартный процесс «три пришли из двух, все пришли из трех». От класса до методов, которые мы можем вызвать, используются эти два шага.

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

    Класс Hello на самом деле является «семантическим сокращением» функции, просто для облегчения понимания кода. Другой способ написать это:

    def  fn(self ,name='world' ) : # If we have a function called fn
         print ( 'Hello, %s.' % name )
    
    
    Hello = type ('Hello',(object,),dict(say_hello = fn))
    # Create Hello class by type ---- Mysterious "Truth", you can change everything, this time we directly from the "Truth" gave birth to "2" 
    

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

    # Create an instance of hello from the Hello class.
    
    hello = Hello ()
    
    
    
    # Use hello call method say_hello ---- all three things, exactly the same
    
    Hello . say_hello () 
    

    Выходной эффект:

    Hello, world. ---- The result of the call is exactly the same. 
    

    Мы оглянулись на самое захватывающее место. Дорога родила сразу двоих:

    Hello = type('Hello', (object,), dict(say_hello=fn)) 
    

    Это «Истина», происхождение мира питонов. Вы можете удивиться этому.

    Обратите внимание на три его параметра! Три вечных суждения, которые совпадают с человечеством: кто я, откуда я, куда я иду?

    The first parameter: who I am. Here, I need a name that distinguishes everything else. The above example names me "Hello."
    
    The second parameter: where do I come from. Here, I need to know where I come from, which is my "parent". In my example above, my parent is "object" - a very primitive class in Python.
    
    The third parameter: Where do I go? Here, we include the methods and properties that we need to call into a dictionary and pass them as parameters. In the above example, we have a say_hello method packed into a dictionary. 
    

    Стоит отметить, что тремя основными вечными суждениями являются все классы, все экземпляры и даже все свойства и методы экземпляров. Как и должно быть, их «создатели», «Правда» и «Один», а именно тип и метакласс, также имеют эти три параметра. Но обычно три вечных предложения класса не передаются в качестве параметров, а передаются следующим образом:

    class Hello(object):{
    
    After class # statement "Who Am I?"
    
    # In the parentheses declare "where do I come from"
    
    # In curly brackets declare "Where do I go?"
    
         def say_hello ():{
    
    
    
         }
    
    } 
    
    
    The Creator can create a single person directly, but this is a hard labor. The Creator will first create the species "human" and then create a specific individual in batches. And pass on the three eternal propositions.
    
    "Truth" can produce "2" directly, but it will produce "1" and then make "2" in batches.
    
    Type can directly generate a class, but it can also be a metaclass and then use a metaclass to customize a class. 
    

    Метакласс - Один пришел от Истины, два - от одного.

    В общем, метаклассы называются суффиксом Metaclass. Представьте, что нам нужен метакласс, который может автоматически сказать «привет». Методы класса в нем иногда требуют say_Hello, иногда say_Hi, иногда say_Sayolala, а иногда say_Nihao.

    Если каждый встроенный say_xxx должен быть объявлен один раз в классе, как ужасно тяжело это будет! Для решения проблемы лучше использовать метаклассы.

    Ниже приведен код мета-класса для создания специального приветствия:

    class SayMetaClass(type):
    
    
    
         def __new__ (cls, Name ,Bases ,Attrs) :
    
             attrs[ 'say_' + name ] = lambda   self, value , saying = name : print ( saying + ',' + value + '!' )
    
             Return   Type . __new__ ( cls ,name, bases ,   attrs) 
    

    Помните две вещи:

    Metaclasses are derived from "type", so the parent class needs to pass in the type. [Taosheng 1, so one must include Tao]
    
    Metaclass operations are done in __new__. The first parameter is the class that will be created. The following parameters are the three eternal propositions: Who am I, where do I come from, and where do I go. The objects it returns are also the three eternal propositions. Next, these three parameters will always be with us.
    

    В , новом , я выполнил только одну операцию.

    Attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!') 
    

    Создает метод класса с именем класса. Например, класс, который мы создали из метакласса, называется «Hello». Когда он был создан, он автоматически получал метод класса с именем "say_Hello". Затем он будет использовать имя класса «Hello» в качестве параметра по умолчанию и передать его методу. Затем передайте вызов метода hello в качестве значения и, наконец, распечатайте его.

    Итак, как метакласс переходит от создания к вызову?

    Приходите! Вместе с принципами Даошенг, Ишэнъю, Бишенгсан, Саньшену, войдите в жизненный цикл класса Юань!

    # Tao Shengyi: incoming type
    
    class SayMetaClass(type):
    
    
    
         # Incoming three eternal propositions: class name, parent class, attribute
    
         def __new__(cls ,name ,bases ,attrs):
    
             # Create "talent"
    
             attrs[ 'say_' + name ] = lambda   self, value , saying = name : print( saying + ',' + value + '!' )
    
             # Three eternal propositions: class name, parent class, attribute
    
             return type . __new__ ( cls ,name ,bases ,attrs )
    
    
    
    # Lifetime 2: Create class
    
    class Hello ( object ,metaclass = SayMetaClass):
         pass
    
    
    
    # two students three: create a real column
    
    Hello = Hello ()
    
    
    
    # Three things: call the instance method
    
    hello.say_Hello('world!') 
    

    Выход

    Hello, world! 
    

    Примечание: класс, созданный метаклассом, первый параметр - родительский класс, второй параметр - метакласс

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

    Теперь, оставив метакласс без изменений, мы можем продолжить создание класса Sayolala, Nihao, следующим образом:

    # Two came from one: Create class
    
    class Sayolala ( object ,metaclass = SayMetaClass ) :
        pass
    
    
    
    # three came from two: create a real column
    
    s = Sayolala ()
    
    
    
    # all things came from two: call the instance method
    
    s.say_Sayolala ( 'japan!' ) 
    

    Выход

    Sayolala, japan! 
    

    Может также говорить по-китайски

    # Two came from one: Create class
    
    class Nihao(object ,metaclass = SayMetaClass ) :
        pass
    
    
    
    # two students three: create a real column
    
    n = Nihao()
    
    
    
    # Three things: call the instance method
    
    n.say_Nihao ( '中 中华!' ) 
    

    Выход

    Nihao, China! 
    

    Еще один небольшой пример:

    # one came from truth.
    
    class ListMetaclass (type) :
    
         def   __new__ ( cls ,name, bases ,   attrs) :
    
             # Talent: Bind values ​​by the add method
    
             attrs[ 'add' ] = lambda   self, value: self.append(value)
    
             return type . __new__ ( cls ,name ,bases ,attrs )
    
    
    
    # One lifetime
    
    class MyList ( list ,   Metaclass = ListMetaclass ) :
        pass
    
    
    
    # Two students three
    
    L = MyList ()
    
    
    
    # Three things
    
    L.add( 1 ) 
    

    Теперь мы печатаем L

    print(L)
    
    
    
    >>> [ 1 ] 
    

    Обычный список не имеет метода add ()

    L2 = list ()
    
    L2 . add ( 1 )
    
    
    
    >>> AttributeError : 'list'   Object   Has no attribute   'add' 
    

    потрясающе! Узнали ли вы здесь, испытали ли вы радость Творца?

    Everything in the python world is at your fingertips. 
    

    Молодой Создатель, пожалуйста, следуйте за мной, чтобы создать новый мир.

    Мы выбираем две области, одна из которых является основной идеей Django, «Object Relational Mapping», объектно-реляционное отображение, именуемое ORM.

    Это главная трудность Джанго, но после изучения метакласса все становится ясно. Ваше понимание Джанго будет еще лучше!

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

    Эти два навыка очень полезны и очень забавны!

    Задача 1: Создать ORM по метаклассу

    Подготовьтесь к созданию класса поля

    class Field ( object ) :
         def __init__ ( self, name, column_type ) :
    
             Self.name = name
    
             Self.column_type = column_type
    
    
    
         def   __str__ ( self ) :
    
             return   '
    ответ дан pah8J, с репутацией 383, 19.06.2018
  • 9 рейтинг

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

    Например,

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

    def func(self, name='mike'):
        print('Hi, %s.' % name)
    
    Hi = type('Hi', (object,), dict(hi=func))
    h = Hi()
    h.hi()
    Hi, mike.
    
    type(Hi)
    type
    
    type(h)
    __main__.Hi
    

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

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

    ответ дан binbjz, с репутацией 296, 12.01.2018
  • 0 рейтинг

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