Home

Tags

Metaclass python

2011-06-25 python metaclass

Класс позволяет создать экземпляры класса, метакласс позволяет контролировать создание класса и его экземпляры.
Метакласс может являтся фабрикой классов.
Примеры для Python 2.7

Рассмотрим методы создания класса:

Создаем класс с типом type:
class A(object):
    pass

Аналогично предыдущему:
B = type('B',(object,),{})

Аналогично предыдущему, но явно указываем тип type
C = type.__new__(type, 'C', (object,), {} )

Создадим свой тип Meta, и сделаем с помощью его класс
class Meta(type):
    pass
D = type.__new__( Meta , 'D', (object,), {} )

Посмотрим типы наших классов:
class A(object):
    pass
B = type('B',(object,),{})
C = type.__new__(type, 'C', (object,), {} )

class Meta(type):
    pass
D = type.__new__( Meta , 'D', (object,), {} )

print A, type(A)
print B, type(B)
print C, type(C)
print D, type(D)
Результат
<class '__main__.A'> <type 'type'>
<class '__main__.B'> <type 'type'>
<class '__main__.C'> <type 'type'>
<class '__main__.D'> <class '__main__.Meta'>
Классы A, B, C имеют тип type, Класс D имеет тип Meta.
Meta является метакласом, позволяет контролировать создание класса и его экземпляры.

Создание экземпляра класса

a = A()

Аналогично предыдущему:
a = type.__call__(A)

Переопределение методов метакласса

Как мы видим, у типа type есть 3 интересных метода:
type.__new__ (создание класса)
type.__init__ (инициализация класса)
type.__call__ (создание экземпляра),
в нашем метаклассе Meta их можно переопределить, при этом в создаваемом классе нужно будет указать metaclass
class Meta(type):
    def __new__(cls, name, base, dic):
        print 'new:  %r = %r = %r' % (cls, name, base)
        return type.__new__(cls,name,base,dic)
    def __init__(cls, *args, **kwargs):
        print 'init:', cls
    def __call__(cls):
        print 'call:', cls
        return type(cls)

print 'Make class'
class Base():
    __metaclass__ = Meta

class Foo(Base):
    pass

print 'Make instance'
b = Base()
f = Foo()
Результат
Make class
new:  <class '__main__.Meta'> = 'Base' = ()
init: <class '__main__.Base'>
new:  <class '__main__.Meta'> = 'Foo' = (<class '__main__.Base'>,)
init: <class '__main__.Foo'>
Make instance
call: <class '__main__.Base'>
call: <class '__main__.Foo'>
Метакласс передается по наследству, поэтому его в класс Foo не нужно указывать т.к. он отнаследован от Base.

Примеры

Расширение класса при создании
class Car():
    def name(self):
        print 'Car'
class Bus():
    def name(self):
        print 'Bus'

class Meta(type):
    def __new__(cls, name, base, dic):
        if (len(base) == 2) and (type(base[1]) == str):
            base = ( base[0], globals()[base[1]] )
        dic['color'] = 'red'
        return type.__new__(cls,name,base,dic)

class Base():
    __metaclass__ = Meta

class Machine(Base,'Bus'):
    speed = 100

m = Machine()
m.name()
print m.speed
print m.color
Результат
Bus
100
red


Расширение класса при создании экземпляра
class Meta(type):
    def __call__(cls, arg):
        t = type(arg)               # Определяем тип параметра
        t2 = type('t2',(cls,t), {}) # Создаем класс на основе обрабатываемого класса и типа входящего параметра
        i = t.__new__(t2, arg)      # Создаем экземпляр типа входного параметра ( + инициализация типов int, str...)
        t.__init__(i, arg)          # Инициализация
        return i

class Base():
    __metaclass__ = Meta

class Combine(Base):
    def name(self):
        print 'Combine\n'

a = Combine(10)
print a
a.name()

b = Combine([1,2,3])
print b
b.name()

c = Combine({ 'id':'hello' })
print c
c.name()
Результат
10
Combine

[1, 2, 3]
Combine

{'id': 'hello'}
Combine