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

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

5145 голосов
/ 19 сентября 2008

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

Ответы [ 15 ]

6257 голосов
/ 05 июля 2011

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

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

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

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

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

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

Да, объекты.

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

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

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

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

Но все же, это объект, и поэтому:

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

например:.

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

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

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

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

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

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

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

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

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

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

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

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

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

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

например:.

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

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

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

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

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

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

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

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

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

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

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

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

будет:

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

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

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

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

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

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

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

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

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

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

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

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

MyClass = MetaClass()
my_object = MyClass()

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

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

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

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

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

Это можно увидеть, проверив атрибут __class__.

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

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

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

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

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

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

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

Атрибут __metaclass__

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

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

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

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

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

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

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

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

class Foo(Bar):
    pass

Python выполняет следующие действия:

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

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

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

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

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

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

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

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

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

Вы обычно делаете это для 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)

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

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

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

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

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

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

2477 голосов
/ 19 сентября 2008

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

В то время как в 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__
352 голосов
/ 19 сентября 2008

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

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

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

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

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

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

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

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

    return "yellow"

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

print 'TestName = ', repr(TestName)

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

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

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

    return type(name, bases, dict)

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

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

Обратите внимание, что магическое поведение, которое «инициализирует», приобретая метакласс 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
144 голосов
/ 21 июня 2011

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

144 голосов
/ 19 сентября 2008

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

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

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

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

105 голосов
/ 19 сентября 2008

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

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

92 голосов
/ 11 августа 2015

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

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

псевдокод:

>>> Class(...)
instance

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

>>> Metaclass(...)
Class

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

class Foo(object): 
    'demo'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from collections import OrderedDict

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

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

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

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

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

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

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

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

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

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
66 голосов
/ 01 марта 2016

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

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

  • __prepare__ и
  • __new__

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

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

Метаклассу «без косточек, ничего не делать» хотелось бы:

class Meta(type):

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

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

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

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

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

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

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

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

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

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

Образец прогона:

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

производит:

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

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

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

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value
57 голосов
/ 13 октября 2016

Роль метакласса '__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__() экземпляра. Это также тот, который в конечном итоге возвращает экземпляр.

Из вышесказанного вытекает, что метаклассу '1045 * также предоставляется возможность решить, будет ли в конечном итоге сделан вызов 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
50 голосов
/ 25 февраля 2014

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

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

#!/usr/bin/env python

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

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

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

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

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

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

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

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

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

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

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

            type.__setattr__(GsyncListOptions, name, listvalue)

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

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()
...