Download - Магия метаклассов
Магия метаклассовАндрей Захаревич
Что такое объект• поля • методы
>>> foo = Foo() >>> foo.bar 'bar' >>> foo.baz() 'baz' >>> print foo <__main__.Foo object at 0x109e48a10>
Что такое класс
class Foo(object): bar = 'bar' def baz(self): return 'baz'
Что такое классКласс — тоже объект
>>> print Foo <class '__main__.Foo'> >>> print hasattr(Foo, 'new_attribute') False >>> Foo.new_attribute = 'foo' >>> print hasattr(Foo, 'new_attribute') True >>> print Foo.new_attribute 'foo'
Класс — это объект>>> 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>
Не очень динамично?
Функция type
>>> print type(1) <type 'int'> >>> print type('1') <type 'str'> >>> print type(Foo) <type 'type'> >>> print type(Foo()) <class '__main__.Foo'>
Определение типа…
Функция type
type(<имя класса>, <кортеж родительских классов>, # для наследования, может быть пустым <словарь, содержащий атрибуты и их значения>)
…и не только
Функция typeclass MyShinyClass(object): pass
или же так
>>> MyShinyClass = type('MyShinyClass', (), {}) >>> print MyShinyClass <class '__main__.MyShinyClass'> >>> print MyShinyClass() <__main__.MyShinyClass object at 0x109ee3290>
Посложнее>>> 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 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
Что такое метакласс
• Класс создает объект • Класс — тоже объект • Метакласс создает класс
MyClass = MetaClass() MyObject = MyClass()
type — метакласс
MyClass = type('MyClass', (), {})
Все — объект
>>> 42.__class__ <type 'int'> >>> 'Hello world'.__class__ <type 'str'> >>> def foo(): pass >>> foo.__class__ <type 'function'> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'>
Класс класса
>>> 42.__class__.__class__ <type 'type'> >>> 'Hello world'.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
Атрибут __metaclass__
Приоритеты атрибута __metaclass__:
1. Класс 2. Родительские классы 3. Модуль (только для классов без родителя) 4. type
class Foo(object): __metaclass__ = something... [...]
Метакласс-функция
def upper_attr(future_cls_name, future_cls_parents, future_cls_attr): attrs = {} for name, val in future_cls_attr.items(): if not name.startswith('__'): attrs[name.upper()] = val else: attrs[name] = val
return type(future_cls_name, future_cls_parents, attrs)
Метакласс-функция
class Foo(object): __metaclass__ = upper_attr bar = 'bar'
foo = Foo() print hasattr(foo, 'bar') # False print hasattr(foo, 'BAR') # True print foo.BAR # bar
А теперь классclass UpperAttrMetaclass(type): def __new__(up_metacls, future_cls_name, future_cls_parents, future_cls_attr):
attrs = {} for name, val in future_cls_attr.items(): if not name.startswith('__'): attrs[name.upper()] = val else: attrs[name] = val
return type(future_cls_name, future_cls_parents, attrs)
А теперь класс
class UpperAttrMetaclass(type): def __new__(up_metacls, future_cls_name, future_cls_parents, future_cls_attr):
attrs = {} for name, val in future_cls_attr.items(): if not name.startswith('__'): attrs[name.upper()] = val else: attrs[name] = val
return type.__new__(up_metacls, future_cls_name, future_cls_parents, attrs)
А теперь класс
class UpperAttrMetaclass(type): def __new__(mcs, name, bases, dct):
attrs = {} for name, val in dct.items(): if not name.startswith('__'): attrs[name.upper()] = val else: attrs[name] = val
return super(UpperAttrMetaclass, mcs).\ __new__(mcs, name, bases, attrs)
Магические методы
В обычном классе:
• __new__(cls, *args, **kwargs) — создает инстанс объекта класса
• __init__(self, *args, **kwargs) — инициализирует инстанс объект класса
• __call__(self, *args, **kwargs) — вызывается при попытке вызвать инстанс класса
Магические методы
В метаклассе:
• __new__(mms, name, bases, dct) — создает объект-класс
• __init__(cls, name, bases, dct) — инициализирует объект-класс
• __call__(cls, *args, **kwargs) — вызывается при попытке создать инстанс класса
Переопределение __call__
instance = constructor()
instance = MyClass()
Переопределение __call__class MetaSingleton(type): instance = None def __call__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super(MetaSingleton,cls).\ __call__(*args, **kwargs) return cls.instance
class Foo(object): __metaclass__ = MetaSingleton
a = Foo() b = Foo() assert a is b
А что в python3?
1. Определить нужный метакласс
2. Подготовить пространство имен
3. Выполнить тело класса
4. Создать объект-класс
Определение метакласса
class Meta(type): pass
class MyClass(metaclass=Meta): pass
• Метакласс передается параметром, а не атрибутом класса
• Метаклассов уровня модуля больше нет
Подготовка пространства имен
def __prepare__(mcs, cls, bases, **kwargs): return dict()
Возвращает dict-like объект, в который будут записаны атрибуты класса при интерпретации его тела.
Дополнительные параметры берутся из заголовка
class MyClass(metaclass=Meta, **kwargs):
Выполнение тела класса
exec(body, globals(), namespace)
namespace — то, что вернула функция __prepare__. Если её нет, используется обычный dict.
Создание класса
def __new__(cls, name, bases, namespace, **kwargs): return type(name, bases, namespace)
Вместо словаря атрибутов мы получаем тот dict-like объект, который создали в __prepare__.
Дополнительные параметры те же, что в __prepare__.
Зачем это все?
Скрываем сложности
• ORM
• Учет создаваемых объектов
• Проверка классов
• Прочая магия
ORM
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
guy = Person(name='Bob', age='35') print guy.age
Если не метаклассы, то что?
• Декораторы классов
• Monkey-patching
Спасибо за внимание