что это такое и с чем его едят
Метаклассы – это классы, экземпляры которых являются классами. Давайте поговорим о специфике языка Python и его функционале.
Чтобы создать свой собственный метакласс в Python, нужно воспользоваться подклассом type, стандартным метаклассом в Python. Чаще всего метаклассы используются в роли виртуального конструктора. Чтобы создать экземпляр класса, нужно сначала вызвать этот самый класс. Точно так же делает и Python: для создания нового класса вызывает метакласс. Метаклассы определяются с помощью базовых классов в атрибуте __metaclass__. При создании класса допускается использование методов __init__ и __new__. С их помощью можно пользоваться дополнительными функциями. Во время выполнения оператора class генерируется пространство имен, которое будет содержать атрибуты будущего класса. Затем, для непосредственного создания, вызывается метакласс с именем и атрибутами.
Пример:
def make_hook(f): """Decorator to turn 'foo' method into '__foo__'""" f.is_hook = 1 return f class MyType(type): def __new__(mcls, name, bases, attrs): if name.startswith('None'): return None # Go over attributes and see if they should be renamed. newattrs = {} for attrname, attrvalue in attrs.iteritems(): if getattr(attrvalue, 'is_hook', 0): newattrs['__%s__' % attrname] = attrvalue else: newattrs[attrname] = attrvalue return super(MyType, mcls).__new__(mcls, name, bases, newattrs) def __init__(self, name, bases, attrs): super(MyType, self).__init__(name, bases, attrs) # classregistry.register(self, self.interfaces) print "Would register class %s now." % self def __add__(self, other): class AutoClass(self, other): pass return AutoClass # Alternatively, to autogenerate the classname as well as the class: # return type(self.__name__ + other.__name__, (self, other), {}) def unregister(self): # classregistry.unregister(self) print "Would unregister class %s now." % self class MyObject: __metaclass__ = MyType class NoneSample(MyObject): pass # Will print "NoneType None" print type(NoneSample), repr(NoneSample) class Example(MyObject): def __init__(self, value): self.value = value @make_hook def add(self, other): return self.__class__(self.value + other.value) # Will unregister the class Example.unregister() inst = Example(10) # Will fail with an AttributeError #inst.unregister() print inst + inst class Sibling(MyObject): pass ExampleSibling = Example + Sibling # ExampleSibling is now a subclass of both Example and Sibling (with no # content of its own) although it will believe it's called 'AutoClass' print ExampleSibling print ExampleSibling. __mro__
Перед тем как начинать разбираться в метаклассах, нужно хорошо понимать как работают обычные классы в Python, а они очень своеобразны. В большинстве языков это попросту фрагменты кода, которые описывают создание объекта. Данное суждение истинно и для Python:
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
Но есть один нюанс. Классы в Python это объекты. Когда выполняется оператор class, Python создает в памяти объект с именем ObjectCreator.
>>> class ObjectCreator(object): ... pass ...
Объект способен сам создавать экземпляры, так что это класс. А объект вот почему:
- его можно назначить в качестве переменной
- копируется
- есть возможность добавить к нему атрибуты
- передается в роли параметра функции
>>> print(ObjectCreator) # you can print a class because it's an object <class '__main__.
Если классы в Python – это объекты, значит, как и любой другой объект, их можно создавать на ходу. Пример создания класса в функции с помощью 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>
Но это не слишком-то динамично, так как все равно придется прописывать весь класс самостоятельно.
Исходя из того, что классы являются объектами, можно сделать вывод, что они должны чем-то генерироваться. Во время выполнения оператора
Помните функцию type? Старая добрая функция, позволяющая определить тип объекта:
>>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) <type 'type'> >>> print(type(ObjectCreator())) <class '__main__. ObjectCreator'>
Эта функция может создавать классы на ходу. В качестве параметра type принимает описание класса, и возвращает класс.
Функция 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>
Вероятно, вы обратили внимание на то, что
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', (), {})
Скорее всего, вы задаетесь вопросом: почему имя функции пишется с маленькой буквы?
Скорее всего, это вопрос соответствия со 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__:
class Foo(object): __metaclass__ = something... [...]
Если это сделать, то для создания класса Foo Python будет использовать метакласс.
СТОИТ ПОМНИТЬ!
Если написать class Foo(object), объект класса Foo не сразу создастся в памяти.
Python будет искать __metaclass__. Как только атрибут будет найден, он используется для создания класса Foo. В том случае, если этого не произойдет, Python будет использовать type для создания класса.
Если написать:
class Foo(Bar): pass
Python делает следующее:
Проверит, есть ли атрибут __metaclass__ у класса Foo? Если он есть, создаст в памяти объект класса с именем Foo с использованием того, что находится в __metaclass__.
Если Python вдруг не сможет найти __metaclass__, он будет искать этот атрибут на уровне модуля и после этого повторит процедуру. В случае если он вообще не может найти какой-либо __metaclass__, Python использует собственный метакласс type, чтобы создать объект класса.
Теперь вопрос: что можно добавить в __metaclass__?
Ответ: что угодно, что может создавать классы.
А что может создать класс? type или его подклассы, а также всё, что его использует.
Основной целью метакласса является автоматическое изменение класса во время его создания. Обычно это делается для 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 не переопределяется, а вызывается напрямую. Давайте реализуем это:
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. В нём нет ничего особенного: этот метод первым аргументом получает текущий экземпляр. Точно так же, как и 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__. Также с помощью метаклассов можно:
- перехватить создание класса
- изменить класс
- вернуть измененный класс
Есть несколько причин для этого:
- Более понятные названия. Когда вы читаете UpperAttrMetaclass(type), вы знаете что будет дальше.
- Вы можете использовать ООП. Метакласс может наследоваться от метакласса, переопределять родительские методы.
- Можно лучше структурировать свой код. Вряд ли вы будете использовать метаклассы для чего-то простого. Обычно это более сложные задачи. Возможность создавать несколько методов и группировать их в одном классе очень полезна, чтобы сделать код более удобным для чтения.
- Можете использовать __new__, __init__ и __call__. Это открывает простор для творчества. Обычно все это можно сделать в __new__, но некоторым людям просто удобнее работать в __init__.
«Метаклассы – это магия, о которой 99% пользователей не стоит даже задумываться. Если вам интересно, нужны ли они вам – тогда точно нет. Люди, которым они на самом деле нужны, знают, зачем, и что с ними делать.»
~ Гуру Python Tim Peters
В основном метаклассы используются для создания 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 и даже может взять его непосредственно из базы данных.
Во-первых, классы – это объекты, создающие экземпляры. Классы сами являются экземплярами метаклассов.
>>> class Foo(object): pass >>> id(Foo) 142630324
В Python все является объектами. Все они являются либо экземплярами классов, либо экземплярами метаклассов. За исключением type. type – сам себе метакласс. Его невозможно создать в чистом Python, это можно сделать только при помощи небольшого читерства.
Во-вторых, метаклассы сложны. Если вам не нужны сложные изменения класса, метаклассы использовать не стоит. Просто изменить класс можно двумя способами:
- Руками
- Декораторами класса
В 99% случаев лучше использовать эти методы, а в 98% изменения класса вообще не нужны.
Оригинал
- Самые эффективные ресурсы и материалы для изучения Python.
- Изучение Python: ТОП-10 вопросов разной направленности.
Метаклассы в Python / Хабр
Как сказал один из пользователей StackOverflow, «using SO is like doing lookups with a hashtable instead of a linked list». Мы снова обращаемся к этому замечательному ресурсу, на котором попадаются чрезвычайно подробные и понятные ответы на самые различные вопросы.
В этот раз мы обсудим, что такое метаклассы, как, где и зачем их использовать, а также почему обычно этого делать не стоит.
Перед тем, как изучать метаклассы, надо хорошо разобраться с классами, а классы в Питоне — вещь весьма специфическая (основаны на идеях из языка Smalltalk).
В большинстве языков класс это просто кусок кода, описывающий, как создать объект. В целом это верно и для Питона:
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print my_object <__main__.ObjectCreator object at 0x8974f2c>
Но в Питоне класс это нечто большее — классы также являются объектами.
Как только используется ключевое слово class
, Питон исполняет команду и создаёт объект. Инструкция
>>> class ObjectCreator(object): ... pass ...
создаст в памяти объект с именем ObjectCreator
.
Этот объект (класс) сам может создавать объекты (экземпляры), поэтому он и является классом.
Тем не менее, это объект, а потому:
- его можно присвоить переменной,
- его можно скопировать,
- можно добавить к нему атрибут,
- его можно передать функции в качестве аргумента,
Так как классы являются объектами, их можно создавать на ходу, как и любой объект.
Например, можно создать класс в функции, используя ключевое слово class
:
>>> def choose_class(name): ... if name == 'foo': ... class Foo(object): ... pass ... return Foo # возвращает класс, а не экземпляр ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class('foo') >>> print MyClass # функция возвращает класс, а не экземпляр <class '__main__.Foo'> >>> print MyClass() # можно создать экземпляр этого класса <__main__. Foo object at 0x89c6d4c>
Однако это не очень-то динамично, поскольку по-прежнему нужно самому писать весь класс целиком.
Поскольку классы являются объектами, они должны генерироваться чем-нибудь.
Когда используется ключевое слово class
, Питон создаёт этот объект автоматически. Но как и большинство вещей в Питоне, есть способ сделать это вручную.
Помните функцию type
? Старая-добрая функция, которая позволяет определить тип объекта:
>>> print type(1) <type 'int'> >>> print type("1") <type 'str'> >>> print type(ObjectCreator) <type 'type'> >>> print type(ObjectCreator()) <class '__main__.ObjectCreator'>
На самом деле, у функции type
есть совершенно иное применение: она также может создавать классы на ходу. type
принимает на вход описание класса и созвращает класс.
(Я знаю, это по-дурацки, что одна и та же функция может использоваться для двух совершенно разных вещей в зависимости от передаваемых аргументов. Так сделано для обратной совместимости)
type
работает следующим образом:
type(<имя класса>, <кортеж родительских классов>, # для наследования, может быть пустым <словарь, содержащий атрибуты и их значения>)
Например,
>>> class MyShinyClass(object): ... pass
может быть создан вручную следующим образом:
>>> MyShinyClass = type('MyShinyClass', (), {}) # возвращает объект-класс >>> print MyShinyClass <class '__main__.MyShinyClass'> >>> print MyShinyClass() # создаёт экземпляр класса <__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') >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
Уже понятно, к чему я клоню: в Питоне классы являются объектами и можно создавать классы на ходу.
Это именно то, что Питон делает, когда используется ключевое слово class
, и делает он это с помощью метаклассов.
Метакласс это «штука», которая создаёт классы.
Мы создаём класс для того, чтобы создавать объекты, так? А классы являются объектами. Метакласс это то, что создаёт эти самые объекты. Они являются классами классов, можно представить это себе следующим образом:
MyClass = MetaClass() MyObject = MyClass()
Мы уже видели, что type
позволяет делать что-то в таком духе:
MyClass = type('MyClass', (), {})
Это потому что функция type
на самом деле является метаклассом. type
это метакласс, который Питон внутренне использует для создания всех классов.
Естественный вопрос: с чего это он его имя пишется в нижнем регистре, а не Type
?
Я полагаю, это просто для соответствия str
, классу для создания объектов-строк, и int
, классу для создания объектов-целых чисел. type
это просто класс для создания объектов-классов.
Это легко проверить с помощью атрибута __class__
:
В питоне всё (вообще всё!) является объектами. В том числе числа, строки, функции и классы — они все являются объектами и все были созданы из класса:
>>> 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__
?
>>> a.__class__.__class__ <type 'type'> >>> age.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
Итак, метакласс это просто штука, создающая объекты-классы.
Если хотите, можно называть его «фабрикой классов»
type
это встроенный метакласс, который использует Питон, но вы, конечно, можете создать свой.
При написании класса можно добавить атрибут __metaclass__
:
class Foo(object): __metaclass__ = something... [...]
В таком случае Питон будет использовать указанный метакласс при создании класса Foo
.
Осторожно, тут есть тонкость!
Хоть вы и пишете class Foo(object)
, объект-класс пока ещё не создаётся в памяти.
Питон будет искать __metaclass__
в определении класса. Если он его найдёт, то использует для создания класса Foo
. Если же нет, то будет использовать type
.
То есть когда вы пишете
class Foo(Bar): pass
Питон делает следующее:
Есть ли у класса Foo
атрибут __metaclass__
?
Если да, создаёт в памяти объект-класс с именем Foo
, используя то, что указано в __metaclass__
.
Если Питон не находит __metaclass__
, он ищет __metaclass__
в родительском классе Bar
и попробует сделать то же самое.
Если же __metaclass__
не находится ни в одном из родителей, Питон будет искать __metaclass__
на уровне модуля.
И если он не может найти вообще ни одного __metaclass__
, он использует type
для создания объекта-класса.
Теперь важный вопрос: что можно положить в __metaclass__
?
Ответ: что-нибудь, что может создавать классы.
А что создаёт классы? type
или любой его подкласс, а также всё, что использует их.
Основная цель метаклассов — автоматически изменять класс в момент создания.
Обычно это делает для API, когда хочется создавать классы в соответсвии с текущим контекстом.
Представим глупый пример: вы решили, что у всех классов в вашем модуле имена атрибутов должны быть записать в верхнем регистре. Есть несколько способов это сделать, но один из них — задать __metaclass__
на уровне модуля.
В таком случае все классы этого модуля будут создаваться с использованием указанного меакласса, а нам остаётся только заставить метакласса переводить имена всех атрибутов в верхний регистр.
К счастью, __metaclass__
может быть любым вызываемым объектом, не обязательно формальным классом (я знаю, что-то со словом «класс» в названии не обязано быть классом, что за ерунда? Однако это полезно).
Так что мы начнём с простого примера, используя функцию.
# метаклассу автоматически придёт на вход те же аргументы, # которые обычно используются в `type` def upper_attr(future_class_name, future_class_parents, future_class_attr): """ Возвращает объект-класс, имена атрибутов которого переведены в верхний регистр """ # берём любой атрибут, не начинающийся с '__' attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) # переводим их в верхний регистр uppercase_attr = dict((name.upper(), value) for name, value in attrs) # создаём класс с помощью `type` return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # это сработает для всех классов в модуле class Foo(object): # или можно определить __metaclass__ здесь, чтобы сработало только для этого класса bar = 'bip' print hasattr(Foo, 'bar') # Out: False print hasattr(Foo, 'BAR') # Out: True f = Foo() print f.BAR # Out: 'bip'
А теперь то же самое, только используя настояший класс:
# помним, что `type` это на само деле класс, как `str` и `int`, # так что от него можно наследовать class UpperAttrMetaclass(type): # Метод __new__ вызывается перед __init__ # Этот метод создаёт обхект и возвращает его, # в то время как __init__ просто инициализирует объект, переданный в качестве аргумента. # Обычно вы не используете __new__, если только не хотите проконтролировать, # как объект создаётся # В данном случае созданный объект это класс, и мы хотим его настроить, # поэтому мы перегружаем __new__. # Можно также сделать что-нибудь в __init__, если хочется. # В некоторых более продвинутых случаях также перегружается __call__, # но этого мы сейчас не увидим. def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) 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): attrs = ((name, value) for name, value in future_class_attr. items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) # используем метод type.__new__ # базовое ООП, никакой магии return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
Вы, возможно, заметили дополнительный аргумент upperattr_metaclass
. Ничего особого в нём нет: метод всегда получает первым аргументом текущий экземпляр. Точно так же, как вы используете self
в обычным методах.
Конечно, имена, которые я тут использовал, такие длинные для ясности, но как и self
, есть соглашение об именовании всех этих аргументов. Так что реальный метакласс выгляит как-нибудь так:
class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) uppercase_attr = dict((name. upper(), value) for name, value in attrs) return type.__new__(cls, name, bases, uppercase_attr)
Можно сделать даже лучше, использовав super
, который вызовет наследование (поскольку, конечно, можно создать метакласс, унаследованный от метакласса, унаследованного от type
):
class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return super(UpperAttrMetaclass, cls).__new__(cls, name, 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
Всё что угодно является объектом в Питоне: экземпляром класса или экземпляром метакласса.
Кроме type
.
type
является собственным метаклассом. Это нельзя воспроизвести на чистом Питоне и делается небольшим читерством на уровне реализации.
Во-вторых, метаклассы сложны. Вам не нужно использовать их для простого изменения классов. Это можно делать двумя разными способами:
- руками
- декораторы классов
В 99% случаев, когда вам нужно изменить класс, лучше использовать эти два.
Но в 99% случаев вам вообще не нужно изменять классы 🙂
oop — Что такое метаклассы в Python?
Прежде чем разбираться в метаклассах, полезно глубже понять классы Python. У Python очень своеобразное представление о том, что такое классы, которое он заимствует из языка Smalltalk.
В большинстве языков классы — это просто фрагменты кода, описывающие, как создать объект. Это отчасти верно и для Python:
>>> class ObjectCreator(object): ... проходить >>> мой_объект = ObjectCreator() >>> печать (мой_объект) <__main__. ОбъектCreator с адресом 0x8974f2c>
Но классы в Python — это нечто большее. Классы тоже являются объектами.
Да, объекты.
При запуске сценария Python каждая строка кода выполняется сверху вниз. Когда интерпретатор Python встречает ключевое слово class
, Python создает объект из «описания» следующего за ним класса. Таким образом, следующая инструкция
>>> class ObjectCreator(object): ... проходить
…создает объект с именем ObjectCreator
!
Этот объект (класс) сам по себе способен создавать объекты (называемые экземплярами ).
Но все же это объект. Поэтому, как и все объекты:
- вы можете присвоить его переменной 1
JustAnotherVariable = ObjectCreator
- к нему можно прикрепить атрибуты
ObjectCreator.class_attribute = 'фу'
- вы можете передать его как параметр функции
печать (создатель объектов)
1 Обратите внимание, что простое присвоение его другой переменной не меняет __name__
класса, т. е.
>>> print(JustAnotherVariable) <класс '__main__.ObjectCreator'> >>> print(JustAnotherVariable()) <__main__.ОбъектCreator по адресу 0x8997b4c>
Поскольку классы являются объектами, их можно создавать на лету, как и любой другой объект.
Во-первых, вы можете создать класс в функции, используя класс
:
>>> определение выбрать_класс (имя): ... если имя == 'foo': ... класс Foo (объект): ... проходить ... return Foo # вернуть класс, а не экземпляр ... еще: ... класс Бар (объект): ... проходить ... вернуть Бар ... >>> МойКласс = select_class('foo') >>> print(MyClass) # функция возвращает класс, а не экземпляр <класс '__main__.Foo'> >>> print(MyClass()) # вы можете создать объект из этого класса <__main__.Foo объект в 0x89c6d4c>
Но это не так динамично, так как вам все равно придется писать весь класс самостоятельно.
Поскольку классы являются объектами, они должны быть чем-то созданы.
Когда вы используете ключевое слово class
, Python создает этот объект автоматически. Но
с большинством вещей в Python это дает вам возможность сделать это вручную.
Помните функцию типа
? Старая добрая функция, которая позволяет узнать, что
тип объекта:
>>> print(type(1)) <тип 'целое число'> >>> напечатать(тип("1")) <тип 'строка'> >>> печать (тип (создатель объектов)) <тип 'тип'> >>> print(type(ObjectCreator())) <класс '__main__.ObjectCreator'>
Ну, тип
имеет еще и совершенно другую способность: он может создавать классы на лету. тип
может принимать описание класса в качестве параметров,
и вернуть класс.
(Я знаю, это глупо, что одна и та же функция может иметь два совершенно разных использования в зависимости от параметров, которые вы ей передаете. Это проблема из-за обратного совместимость в Python)
тип
работает следующим образом:
тип(имя, базы, атрибуты)
Где:
-
имя
: имя класса -
баз
: кортеж родительского класса (для наследования может быть пустым) -
attrs
: словарь, содержащий имена и значения атрибутов
например:
>>> класс MyShinyClass(объект): . .. проходить
можно создать вручную следующим образом:
>>> MyShinyClass = type('MyShinyClass', (), {}) # возвращает объект класса >>> печать (MyShinyClass) <класс '__main__.MyShinyClass'> >>> print(MyShinyClass()) # создать экземпляр с классом Объект <__main__.MyShinyClass по адресу 0x8997cec>
Вы заметите, что мы используем MyShinyClass
в качестве имени класса.
и как переменная для хранения ссылки на класс. Они могут быть разными,
но нет причин все усложнять.
тип
принимает словарь для определения атрибутов класса. Итак:
>>> класс Foo(объект): ... бар = Истина
Можно перевести как:
>>> Foo = type('Foo', (), {'bar':True})
И используется как обычный класс:
>>> печать (Фу) <класс '__main__.Foo'> >>> печать (Foo.bar) Истинный >>> f = Фу() >>> напечатать(ф) Объект <__main__.Foo по адресу 0x8a9b84c> >>> print(f.bar) Истинный
И, конечно же, вы можете наследоваться от него, так что:
>>> class FooChild(Foo): . .. проходить
будет:
>>> FooChild = type('FooChild', (Foo,), {}) >>> печать (FooChild) <класс '__main__.FooChild'> >>> print(FooChild.bar) # бар наследуется от Foo Истинный
Со временем вы захотите добавить методы в свой класс. Просто определите функцию с соответствующей подписью и назначьте его как атрибут.
>>> def echo_bar(self): ... распечатать (self.bar) ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') ЛОЖЬ >>> hasattr(FooChild, 'echo_bar') Истинный >>> my_foo = FooChild() >>> my_foo.echo_bar() Истинный
И вы можете добавить еще больше методов после динамического создания класса, точно так же, как добавление методов к обычно созданному объекту класса.
>>> def echo_bar_more(self): ... print('еще один метод') ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more') Истинный
Вы видите, куда мы идем: в Python классы являются объектами, и вы можете динамически создавать классы на лету.
Вот что делает Python, когда вы используете ключевое слово class
, и делает это с помощью метакласса.
Метаклассы — это «материал», который создает классы.
Вы определяете классы для создания объектов, верно?
Но мы узнали, что классы Python — это объекты.
Метаклассы — это то, что создает эти объекты. Это классы классов, вы можете изобразить их так:
MyClass = MetaClass() мой_объект = МойКласс()
Вы видели, что type
позволяет вам делать что-то вроде этого:
MyClass = type('MyClass', (), {})
Это потому, что функция типа
на самом деле является метаклассом. тип
является
метакласс Python использует для создания всех классов за кулисами.
Теперь вы удивляетесь «почему, черт возьми, это написано строчными буквами, а не Тип
?»
Ну, я думаю, это вопрос согласованности с str
, классом, который создает
строковые объекты и int
класс, создающий целочисленные объекты. тип
есть
просто класс, который создает объекты класса.
Это можно увидеть, проверив атрибут __class__
.
Все, и я имею в виду все, является объектом в Python. Это включает целые числа, строки, функции и классы. Все они являются объектами. И все они имеют создан из класса:
>>> age = 35 >>> возраст.__класс__ <тип 'целое число'> >>> имя = 'боб' >>> имя.__класс__ <тип 'строка'> >>> def foo(): пройти >>> foo.__class__ <тип 'функция'> >>> класс Bar(объект): пройти >>> б = бар() >>> б.__класс__ <класс '__main__.Bar'>
Теперь, что такое __class__
любого __class__
?
>>> возраст.__класс__.__класс__ <тип 'тип'> >>> имя.__класс__.__класс__ <тип 'тип'> >>> foo.__class__.__class__ <тип 'тип'> >>> б.__класс__.__класс__ <тип 'тип'>
Итак, метакласс — это просто материал, который создает объекты класса.
Вы можете назвать это «фабрикой классов», если хотите.
тип
— это встроенный метакласс, который использует Python, но, конечно, вы можете создать свой
собственный метакласс.
В Python 2 вы можете добавить атрибут __metaclass__
при написании класса (см. следующий раздел о синтаксисе Python 3):
class Foo(object): __metaclass__ = что-то... [...]
Если вы это сделаете, Python будет использовать метакласс для создания класса Foo
.
Осторожно, это сложно.
Сначала вы пишете class Foo(object)
, но объект класса Foo
не создается
в памяти еще.
Python будет искать __metaclass__
в определении класса. Если найдет,
он будет использовать его для создания класса объектов Foo
. Если это не так, он будет использовать введите
, чтобы создать класс.
Прочтите это несколько раз.
Когда вы делаете:
class Foo(Bar): проходить
Python делает следующее:
Есть ли атрибут __metaclass__
в Foo
?
Если да, создайте в памяти объект класса (я сказал объект класса, оставайтесь со мной здесь) с именем Foo
, используя то, что находится в __metaclass__
.
Если Python не может найти __metaclass__
, он будет искать __metaclass__
на уровне МОДУЛЯ и попытается сделать то же самое (но только для классов, которые ничего не наследуют, в основном классы старого стиля).
Затем, если он вообще не может найти __metaclass__
, он будет использовать собственный метакласс Bar
(первый родитель) (который может быть типом по умолчанию ) для создания объекта класса.
Будьте осторожны, чтобы атрибут __metaclass__
не был унаследован, метакласс родителя ( Bar.__class__
) будет унаследован. Если Bar
использовал атрибут __metaclass__
, который создал Bar
с type()
(а не type.__new__()
), подклассы не наследуют это поведение.
Теперь большой вопрос, что вы можете поместить в __metaclass__
?
Ответ: то, что может создать класс.
А что может создать класс? введите
или что-либо, что является подклассом или использует его.
Синтаксис для установки метакласса был изменен в Python 3:
class Foo(object, metaclass=something): ...
т. е. атрибут __metaclass__
больше не используется, вместо него в списке базовых классов используется аргумент ключевого слова.
Однако поведение метаклассов практически не изменилось.
Одна вещь, добавленная к метаклассам в Python 3, заключается в том, что вы также можете передавать атрибуты как ключевые слова-аргументы в метакласс, например:
класс Foo(объект, метакласс=что-то, kwarg1=значение1, kwarg2=значение2): ...
Прочтите раздел ниже, чтобы узнать, как Python справляется с этим.
Основное назначение метакласса — автоматическое изменение класса, когда он создан.
Обычно вы делаете это для API, где вы хотите создать классы, соответствующие текущий контекст.
Представьте себе глупый пример, когда вы решаете, что все классы в вашем модуле
должны иметь свои атрибуты, написанные в верхнем регистре. Есть несколько способов
сделать это, но один из способов установить __metaclass__
на уровне модуля.
Таким образом, все классы этого модуля будут созданы с использованием этого метакласса, и нам просто нужно сказать метаклассу перевести все атрибуты в верхний регистр.
К счастью, __metaclass__
на самом деле может быть любым вызываемым, он не обязательно должен быть
формальный класс (я знаю, что-то с «классом» в его имени не должно быть
класс, пойди разберись... но это полезно).
Итак, мы начнем с простого примера, используя функцию.
# метакласс автоматически получит тот же аргумент # который вы обычно передаете в `type` def upper_attr (future_class_name, future_class_parents, future_class_attrs): """ Вернуть объект класса со списком его атрибутов, перевернутым в верхний регистр. """ # выберите любой атрибут, который не начинается с '__', и запишите его в верхнем регистре uppercase_attrs = { attr if attr. startswith("__") else attr.upper(): v для attr, v в future_class_attrs.items() } # пусть `type` создаст класс возвращаемый тип (future_class_name, future_class_parents, uppercase_attrs) __metaclass__ = upper_attr # это повлияет на все классы в модуле class Foo(): # global __metaclass__ не будет работать с "object" # но мы можем определить здесь __metaclass__, чтобы воздействовать только на этот класс # и это будет работать с дочерними "объектами" бар = 'бип'
Проверим:
>>> hasattr(Foo, 'bar') ЛОЖЬ >>> hasattr(Фу, 'БАР') Истинный >>> Фу.БАР бип
Теперь давайте сделаем то же самое, но используя настоящий класс для метакласса:
# помните, что `type` на самом деле является классом, подобным `str` и `int` # так что вы можете наследовать от него класс UpperAttrMetaclass (тип): # __new__ - это метод, вызываемый перед __init__ # это метод, который создает объект и возвращает его # в то время как __init__ просто инициализирует объект, переданный в качестве параметра # вы редко используете __new__, за исключением случаев, когда вы хотите контролировать, как объект # создано. # здесь созданный объект является классом, и мы хотим его настроить # поэтому мы переопределяем __new__ # вы можете сделать кое-что и в __init__, если хотите # некоторые расширенные варианты использования также включают переопределение __call__, но мы не будем # видеть это def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v для attr, v в future_class_attrs.items() } возвращаемый тип (future_class_name, future_class_parents, uppercase_attrs)
Давайте перепишем приведенное выше, но с более короткими и реалистичными именами переменных, теперь, когда мы знаем, что они означают:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v для attr, v в attrs. items() } возвращаемый тип (clsname, bases, uppercase_attrs)
Возможно, вы заметили дополнительный аргумент cls
. Есть
ничего особенного в этом нет : __new__
всегда получает класс, в котором он определен, в качестве первого параметра. Так же, как у вас есть self
для обычных методов, которые получают экземпляр в качестве первого параметра или определяющий класс для методов класса.
Но это неправильный ООП. Мы вызываем типа
напрямую, и мы не переопределяем и не вызываем родительский __new__
. Давайте сделаем это вместо этого:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v для attr, v в attrs.items() } возвращаемый тип.__new__(cls, clsname, bases, uppercase_attrs)
Мы можем сделать его еще чище, используя super
, что упростит наследование (потому что да, вы можете иметь метаклассы, наследование от метаклассов, наследование от типа):
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr. startswith("__") else attr.upper(): v для attr, v в attrs.items() } # Python 2 требует передачи аргументов в super: вернуть super(UpperAttrMetaclass, cls).__new__( cls, clsname, bases, uppercase_attrs) # Python 3 может использовать super() без аргументов, который выводит их: return super().__new__(cls, clsname, bases, uppercase_attrs)
А, и в Python 3, если вы делаете этот вызов с ключевыми словами, например:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1): ...
Это переводится в метакласс, чтобы использовать его:
class MyMetaclass(type): def __new__(cls, clsname, bases, dct, kwargs1=по умолчанию): ...
Вот и все. В метаклассах больше ничего нет.
Причина сложности кода с использованием метаклассов не в том, что
метаклассов, это потому, что вы обычно используете метаклассы, чтобы делать извращенные вещи
полагаться на самоанализ, манипулировать наследованием, такими переменными, как __dict__
и т. д.
Действительно, метаклассы особенно полезны для черной магии, и поэтому сложные вещи. Но сами по себе они просты:
- перехватить создание класса
- изменить класс
- вернуть модифицированный класс
Поскольку __metaclass__
может принимать любой вызываемый объект, зачем вам использовать класс
так как это явно сложнее?
Для этого есть несколько причин:
- Намерение ясно. Когда вы читаете
UpperAttrMetaclass(type)
, вы знаете что будет дальше - Вы можете использовать ООП. Метакласс может наследовать от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
- Подклассы класса будут экземплярами его метакласса, если вы указали класс метакласса, но не функцию метакласса.
- Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то тривиального, как в приведенном выше примере. Обычно это что-то сложное. Возможность сделать несколько методов и сгруппировать их в один класс очень полезна для облегчения чтения кода.
- Можно подключить
__new__
,__init__
и__call__
. Что позволит вам делать разные вещи, даже если обычно вы можете делать все это в__new__
, некоторым людям удобнее использовать__init__
. - Это называется метаклассы, черт возьми! Это должно что-то означать!
Теперь большой вопрос. Зачем вам использовать какую-то непонятную функцию, подверженную ошибкам?
Обычно нет:
Метаклассы — это более глубокая магия, 99% пользователей никогда не должны беспокоиться об этом. Если вы задаетесь вопросом, нужны ли они вам, вы не (люди, которые на самом деле нужно, чтобы они знали с уверенностью, что они им нужны и не нужны пояснение почему).
Гуру Python Тим Питерс
Основным вариантом использования метакласса является создание API. Типичным примером этого является Django ORM. Это позволяет вам определить что-то вроде этого:
class Person(models.Model): имя = модели.CharField(max_length=30) возраст = модели.IntegerField()
Но если вы сделаете так:
человек = Человек(имя='боб', возраст='35') печать(человек.возраст)
Он не вернет объект IntegerField
. Он вернет int
и даже может взять его непосредственно из базы данных.
Это возможно, потому что моделей. Модель
определяет __metaclass__
и
он использует некоторую магию, которая превратит Person в
, которую вы только что определили с помощью простых операторов.
в сложный хук к полю базы данных.
Django делает что-то сложное простым, предоставляя простой API и используя метаклассы, воссоздавая код из этого API для выполнения реальной работы за кулисами.
Во-первых, вы знаете, что классы — это объекты, которые могут создавать экземпляры.
На самом деле классы сами по себе являются экземплярами. Из метаклассов.
>>> класс Foo(объект): пройти >>> идентификатор(фу) 142630324
Все является объектом в Python, и все они являются экземплярами классов или экземпляры метаклассов.
Кроме типа
.
тип
на самом деле является собственным метаклассом. Это не то, что вы могли бы
воспроизвести на чистом Python и сделать это, немного обманув реализацию
уровень.
Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простые изменения класса. Вы можете изменить классы, используя два разных метода:
- исправление обезьяны
- класс декораторов
В 99% случаев вам нужно изменить класс, вам лучше использовать их.
Но в 98% случаев вам вообще не нужно менять класс.
python. Каковы некоторые (конкретные) варианты использования метаклассов?
Недавно мне задали тот же вопрос, и я нашел несколько ответов. Я надеюсь, что можно возродить эту ветку, так как я хотел уточнить некоторые из упомянутых вариантов использования и добавить несколько новых.
Большинство метаклассов, которые я видел, делают одно из двух:
Регистрация (добавление класса в структуру данных):
моделей = {} класс ModelMetaclass (тип): def __new__(мета, имя, базы, атрибуты): модели [имя] = cls = type.__new__(мета, имя, базы, атрибуты) вернуть клс Модель класса (объект): __metaclass__ = ModelMetaclass
Всякий раз, когда вы подкласс
Модель
, ваш класс регистрируется в словаремоделей
:>>> класс А (модель): ... проходить ... >>> класс Б(А): ... проходить ... >>> модели {'A': <__main__.A класс в 0x...>, 'B': класс <__main__.B в 0x...>}
Это также можно сделать с помощью декораторов классов:
моделей = {} модель защиты (cls): модели[cls.__name__] = cls вернуть клс @модель класс А (объект): проходить
Или с явной функцией регистрации:
моделей = {} Def register_model (cls): модели[cls. __name__] = cls класс А (объект): проходить модель_регистра(А)
На самом деле, это почти то же самое: вы неблагоприятно упоминаете декораторы классов, но на самом деле это не более чем синтаксический сахар для вызова функции в классе, так что в этом нет никакой магии.
В любом случае преимущество метаклассов в данном случае заключается в наследовании, поскольку они работают для любых подклассов, тогда как другие решения работают только для подклассов, явно оформленных или зарегистрированных.
>>> класс Б(А): ... проходить ... >>> модели {'A': <__main__.A class at 0x...> # Нет B :(
Рефакторинг (изменение атрибутов класса или добавление новых):
класс ModelMetaclass (тип): def __new__(мета, имя, базы, атрибуты): поля = {} для ключа, значение в attrs.items(): если экземпляр (значение, поле): value.name = '%s.%s' % (имя, ключ) поля [ключ] = значение для базы в базах: если hasattr (база, '_fields'): fields. update(base._fields) attrs['_fields'] = поля возвращаемый тип.__new__(мета, имя, базы, атрибуты) Модель класса (объект): __metaclass__ = ModelMetaclass
Всякий раз, когда вы создаете подкласс
Model
и определяете некоторые атрибутыField
, они вводятся со своими именами (например, для получения более информативных сообщений об ошибках) и группируются в словарь_fields
(для легкой итерации, без необходимости просматривать все атрибуты класса и все атрибуты его базовых классов каждый раз):>>> класс А (модель): ... foo = целое () ... >>> класс Б(А): ... бар = строка () ... >>> Б._поля {'foo': Integer('A.foo'), 'bar': String('B.bar')}
Опять же, это можно сделать (без наследования) с помощью декоратора класса:
модель определения (cls): поля = {} для ключа значение в vars(cls).items(): если экземпляр (значение, поле): value.name = '%s.%s' % (cls.__name__, ключ) поля [ключ] = значение для базы в cls. __bases__: если hasattr (база, '_fields'): fields.update(base._fields) cls._fields = поля вернуть клс @модель класс А (объект): Фу = Целое() класс Б(А): бар = строка () # B.bar не имеет названия :( # B._fields is {'foo': Integer('A.foo')} :(
Или явно:
класс А (объект): foo = Целое('A.foo') _fields = {'foo': foo} # Не забудьте также все поля базовых классов!
Хотя, в отличие от вашей пропаганды удобочитаемого и удобного в сопровождении немета-программирования, это гораздо более громоздко, избыточно и подвержено ошибкам:
класс Б(А): бар = строка () # против. класс Б(А): бар = строка («бар») _fields = {'B.bar': бар, 'A.foo': A.foo}
Рассмотрев наиболее распространенные и конкретные варианты использования, единственные случаи, когда вам абсолютно НЕОБХОДИМО использовать метаклассы, — это когда вы хотите изменить имя класса или список базовых классов, потому что после определения эти параметры запекаются в классе, и ни декоратор, ни функция не могут их испечь.
класс Метакласс(тип): def __new__(мета, имя, базы, атрибуты): возвращаемый тип.__new__(meta, 'foo', (int,), attrs) класс Базовый класс (объект): __metaclass__ = Метакласс класс А (базовый класс): проходить класс Б(А): проходить напечатать A.__name__ # foo напечатать B.__name__ # foo print issubclass(B, A) # False print issubclass(B, int) # Истинно
Это может быть полезно в фреймворках для выдачи предупреждений всякий раз, когда определяются классы с похожими именами или неполными деревьями наследования, но я не могу придумать причины, кроме троллинга, для фактического изменения этих значений. Возможно, Дэвид Бизли сможет.
Во всяком случае, в Python 3 метаклассы также имеют метод __prepare__
, который позволяет вам преобразовать тело класса в отображение, отличное от dict
, таким образом поддерживая упорядоченные атрибуты, перегруженные атрибуты и другие крутые штуки:
импорт коллекций Метакласс класса (тип): @классметод def __prepare__(мета, имя, базы, **kwds): возврат коллекций. OrderedDict() def __new__(мета, имя, базы, атрибуты, **kwds): печать (список (атрибуты)) # Делайте больше... класс A (метакласс = метакласс): х = 1 у = 2 # печатает ['x', 'y'], а не ['y', 'x']
класс ListDict(dict): def __setitem__(я, ключ, значение): self.setdefault (ключ, []). добавить (значение) Метакласс класса (тип): @классметод def __prepare__(мета, имя, базы, **kwds): вернуть СписокДикт() def __new__(мета, имя, базы, атрибуты, **kwds): печать (атрибуты ['foo']) # Делайте больше... класс A (метакласс = метакласс): деф фу(я): проходить деф фу(я, х): проходить # печатает [<функция foo по адресу 0x...>, <функция foo по адресу 0x...>], а не <функция foo по адресу 0x...>
Вы можете утверждать, что упорядоченные атрибуты могут быть достигнуты с помощью счетчиков создания, а перегрузка может быть смоделирована с помощью аргументов по умолчанию:
import itertools Атрибут класса (объект): _counter = itertools. count() защита __init__(сам): self._count = Атрибут._counter.next() класс А (объект): х = атрибут () у = Атрибут () A._order = sorted([(k, v) для k, v в vars(A).items() if isinstance(v, Attribute)], ключ = лямбда (k, v): v._count)
класс А (объект): деф _foo0(я): проходить защита _foo1 (я, х): проходить def foo(self, x=None): если x равно None: вернуть себя._foo0() еще: вернуть self._foo1(x)
Помимо того, что он намного уродливее, он еще и менее гибкий: что, если вам нужны упорядоченные литеральные атрибуты, такие как целые числа и строки? Что, если None
является допустимым значением для x
?
Вот творческий способ решить первую проблему:
система импорта Построитель классов (объект): def __call__(я, клс): cls._order = self.frame.f_code.co_names вернуть клс деф заказал(): строитель = строитель() трассировка защиты (кадр, событие, аргумент): строитель. frame = кадр sys.settrace (нет) sys.settrace(трассировка) вернуть строитель @заказал() класс А (объект): х = 1 у = 'фу' напечатать A._order # ['x', 'y']
А вот и творческий способ решить вторую:
_undefined = object() класс А (объект): деф _foo0(я): проходить защита _foo1 (я, х): проходить def foo(self, x=_undefined): если x равно _undefined: вернуть себя._foo0() еще: вернуть self._foo1(x)
Но это гораздо, ГОРАЗДО шаманство, чем простой метакласс (особенно первый, от которого действительно плавится мозг). Я хочу сказать, что вы смотрите на метаклассы как на незнакомые и противоречащие интуиции, но вы также можете смотреть на них как на следующий шаг эволюции языков программирования: вам просто нужно изменить свое мышление. В конце концов, вы, вероятно, могли бы сделать все в C, включая определение структуры с указателями на функции и передачу ее в качестве первого аргумента ее функциям. Человек, впервые увидевший C++, может сказать: «Что это за магия? Почему компилятор неявно передает это
методам, а не обычным и статическим функциям? Лучше быть ясным и многословным в своих аргументах». Но тогда объектно-ориентированное программирование становится намного более мощным, как только вы его освоите; так же, как и это… квазиаспектно-ориентированное программирование, я полагаю. понимать метаклассы, они на самом деле очень просты, так почему бы не использовать их, когда это удобно?
И, наконец, метаклассы — это круто, и программирование должно приносить удовольствие. Ваше воображение. Поживите немного! Вот метаметакласс специально для вас.
класс MetaMetaclass (тип): def __new__(мета, имя, базы, атрибуты): def __new__(мета, имя, базы, атрибуты): cls = type.__new__(мета, имя, базы, атрибуты) cls._label = 'Сделано в %s' % meta.__name__ вернуть клс attrs['__new__'] = __new__ возвращаемый тип.