18. Метаклассы и метапрограммирование.
Метаклассы
Metaclasses are deeper magic than 99% of users should never worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).
by Tim Peters
Классы и классы классов
- Классы сами по себе являются объектами типа type.
- Это верно для всех new-style классов.
print type(type)
<type 'type'>
- type является предком себя - такую циклическую зависимость нельзя сделать в чистом Python, поэтом она реализуется небольшим хаком в реализации Python.
- type имеет несколько значений в Python:
- type() - как функция возвращая тип переданного ей объекта.
- type - как конструктор типов.
- type - как корневой тип для метаклассов, предок всех классов (подробности ниже).
Метапрограммирование
- В общем случае, программы которые порождают другие программы или динамически модифицируются.
- В частном случае, возможность динамического создания новых типов во время выполнения.
type - конструктор типов
type(name, bases, dct)
- name - имя для создаваемого класса.
- bases - tuple задающий родительские классы для конструируемого класса.
- dct - словарь аттрибутов и методов создаваемого класса.
Следующие примеры дадут идентичные результаты:
class Foo(object): i = 4 class Bar(Foo): def get_i(self): return self.i b = Bar() print b.get_i()
4
Foo = type('Foo', (), dict(i=4)) Bar = type('Bar', (Foo,), dict(get_i = lambda self: self.i)) b = Bar() print b.get_i()
4
type как корневой тип для метаклассов
Мы можем создать собственный метакласс отнаследовавшись от type, а затем использовать его вместо type при создании классов. При создании нового класса с помощью ключевого слова class происходит следующее:
- Python выполняет тело внутри конструкции class как обычный блок кода.
- Получившийся namespace (словарь) содержит в себе аттрибуты будущего класса.
- Метакласс определяется путём поиска поля
__metaclass__
в создаваемом классе, его предках (заданные метаклассы наследуются) или глобальной переменной__metaclass__
. - Найденный метакласс вызывается, ему передаётся имя, список базовых классов и аттрибуты создаваемого класса.
Пример метакласса - регистрируем подклассы
Следующий метакласс просто добавляет словарь registry (реестр) и каждый раз при создании нового класса кладёт в registry имя этого класса.
class DBInterfaceMeta(type): # we use __init__ rather than __new__ here because we want # to modify attributes of the class *after* they have been # created def __init__(cls, name, bases, dct): if not hasattr(cls, 'registry'): # this is the base class. Create an empty registry cls.registry = {} else: # this is a derived class. Add cls to the registry interface_id = name.lower() cls.registry[interface_id] = cls super(DBInterfaceMeta, cls).__init__(name, bases, dct)
Мы можем задать классу другой (вместо type) метакласс путём присваивания его специальному полю __metaclass__
:
class DBInterface(object): __metaclass__ = DBInterfaceMeta print(DBInterface.registry)
{}
После добавления подклассов:
class FirstInterface(DBInterface): pass class SecondInterface(DBInterface): pass class SecondInterfaceModified(SecondInterface): pass print(DBInterface.registry)
{'firstinterface': <class '__main__.FirstInterface'>, 'secondinterface': <class '__main__.SecondInterface'>, 'secondinterfacemodified': <class '__main__.SecondInterfaceModified'>}
Составной пример метакласса
В Python существует два метода, которые используются при создании объекта (в т.ч. классов и метаклассов):
__new__
- конструктор, вызывается непосредственно для создания объекта, редко когда нужен (если нам надо иметь контроль над созданием объекта).__init__
- инициализатор, вызывается после создания объекта для его инициализации, в основном используется этот метод.
В случае с метаклассами редко, но может иметь смысл перегрузка функции __call__
в метаклассе (вызывается, когда идёт создание объекта с помощью имени класса MyClass()).
По сути, эти три функции позволяют влиять на разные этапы создания класса и его объектов.
def make_hook(f): """Decorator to turn 'foo' method into '__foo__'""" f.is_hook = 1 return f class MyType(type): def __new__(cls, 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, cls).__new__(cls, name, bases, newattrs) def __init__(self, name, bases, attrs): super(MyType, self).__init__(name, bases, attrs) print "__init__ for {}".format(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 say_hello(self): print "Hello, {}".format(self)
class MyObject: __metaclass__ = MyType
__init__ for <class '__main__.MyObject'>
class NoneSample(MyObject): pass # Will print "NoneType None" print type(NoneSample), repr(NoneSample)
<type 'NoneType'> None
class Example(MyObject): def __init__(self, value): self.value = value @make_hook def add(self, other): return self.__class__(self.value + other.value) Example.say_hello()
__init__ for <class '__main__.Example'> Hello, <class '__main__.Example'>
inst = Example(10) print inst + inst
<__main__.Example object at 0x7fba581677d0>
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__
__init__ for <class '__main__.Sibling'> __init__ for <class '__main__.AutoClass'> <class '__main__.AutoClass'> (<class '__main__.AutoClass'>, <class '__main__.Example'>, <class '__main__.Sibling'>, <class '__main__.MyObject'>, <type 'object'>)