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

Классы и классы классов

print type(type)
<type 'type'>

Метапрограммирование

type - конструктор типов

type(name, bases, 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 происходит следующее:

Пример метакласса - регистрируем подклассы

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

В случае с метаклассами редко, но может иметь смысл перегрузка функции __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'>)