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




7 Answers

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

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

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

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

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

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

Да, объекты.

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

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

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

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

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

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

например:

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

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

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

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

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

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

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

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

Помните type функции? Хорошая старая функция, которая позволяет вам узнать, какой тип объекта:

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

Ну, type имеет совершенно разные возможности, он также может создавать классы «на лету». type может взять описание класса в качестве параметров и вернуть класс.

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

type работает таким образом:

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

например:

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

могут быть созданы вручную таким образом:

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

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

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

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

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

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

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

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

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

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

было бы:

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

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

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

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

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

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

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

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

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

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

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

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

MyClass = MetaClass()
my_object = MyClass()

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

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

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

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

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

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

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

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

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

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

Итак, метакласс - это всего лишь материал, создающий объекты класса.

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

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

__metaclass__

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

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

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

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

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

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

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

Когда вы выполните:

class Foo(Bar):
    pass

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

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

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

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

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

f = Foo()
print(f.BAR)
# Out: 'bip'

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

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

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

        return type(future_class_name, future_class_parents, uppercase_attr)

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

class UpperAttrMetaclass(type):

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

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

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

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

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

class UpperAttrMetaclass(type):

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

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

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

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

class UpperAttrMetaclass(type):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Python Guru Тим Петерс

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

метаклассы c++

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




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

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

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

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




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

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

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

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

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




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

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

  • __prepare__ , а также
  • __new__

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

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

Необычный, не имеющий ничего лишнего метакласса:

class Meta(type):

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

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

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

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

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

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

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

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

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

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

Пример:

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

производит:

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

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

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

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



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

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

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



Версия tl; dr

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

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

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

class Foo(object):
    __metaclass__ = MyMetaClass



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

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

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

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

type(Hi)
type

type(h)
__main__.Hi

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

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

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

class CustomList(list, metaclass=ListMetaclass):
    pass

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

lst
['custom_list_1', 'custom_list_2']

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






Related


Tags

python   oop   metaclass   python-datamodel