Slide 1

Slide 1 text

Препарируем Django: Модели Владимир Филонов

Slide 2

Slide 2 text

Модели - простые классы? Препарируем Django: Модели На вид вполне обычный класс class RegularClass(object): field = models.CharField(max_length=150) class ModelClass(models.Model): field = models.CharField(max_length=150)

Slide 3

Slide 3 text

Модели - простые классы? Препарируем Django: Модели Сравним поведение экземпляров >>> print ModelClass().field '' >>> print RegularClass().field

Slide 4

Slide 4 text

Модели - простые классы? Препарируем Django: Модели А теперь сравним сами классы >>> print RegularClass.field >>> print ModelClass.field Traceback (most recent call last): File "", line 1, in AttributeError: type object 'ModelClass' has no attribute 'field'

Slide 5

Slide 5 text

Модели - простые классы? Препарируем Django: Модели Куда-то пропало наше поле. Значит не так все просто?

Slide 6

Slide 6 text

class Model(object): __metaclass__ = ModelBase ... Модели - простые классы? Препарируем Django: Модели Посмотрим, что же там внутри

Slide 7

Slide 7 text

class Model(object): __metaclass__ = ModelBase ... Модели - простые классы? Препарируем Django: Модели Посмотрим, что же там внутри

Slide 8

Slide 8 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child"

Slide 9

Slide 9 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Сам метакласс

Slide 10

Slide 10 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Имя класса, который будет сгенерирован "Parent" "Main"

Slide 11

Slide 11 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Список классов-родителей [object] [Parent]

Slide 12

Slide 12 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Словарь аттрибутов будущего класса

Slide 13

Slide 13 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Словарь аттрибутов будущего класса Parent { '__metaclass__': < class '__main__.Base' > , '__module__': '__main__' }

Slide 14

Slide 14 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Словарь аттрибутов будущего класса Main { 'data': 'child', '__module__': '__main__' }

Slide 15

Slide 15 text

Метаклассы - как это работает? Препарируем Django: Модели Метакласс - это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child"

Slide 16

Slide 16 text

Метаклассы - как это работает? Препарируем Django: Модели Что получилось? >>> print dir(Parent) ['HACKED', '__class__', '__delattr__', '__dict__', '__doc__', ...] >>> print dir(Main) ['HACKED', '__class__', '__delattr__', '__dict__', '__doc__', ..., 'data']

Slide 17

Slide 17 text

Метаклассы - как это работает? Препарируем Django: Модели Забавно, но какой практический смысл? • Модификация класса на ходу • Все это происходит при интерпретации кода

Slide 18

Slide 18 text

Метаклассы - как это работает? Препарируем Django: Модели Допустим что нам надо посчитать количество вызовов всех методов. Сделаем вот такой декоратор. def calls_counter(name, func): counter_attr_name = "_{0}_calls_counter".format(name) def counter(instance, *args, **kwargs): setattr(instance, counter_attr_name, getattr(instance, counter_attr_name, 0) + 1) return func(instance, *args, **kwargs) return counter

Slide 19

Slide 19 text

Метаклассы - как это работает? Препарируем Django: Модели Как же нам обернуть сотни методов в десятке классов? class MethodCounterBase(type): def __new__(cls, name, bases, attrs): new_cls = super(MethodCounterBase, cls).__new__(cls, name, bases, attrs) for obj_name, obj in attrs.items(): if callable(obj) and not isinstance(obj, type): setattr(new_cls, obj_name, calls_counter(obj_name, obj)) return new_cls

Slide 20

Slide 20 text

Метаклассы - как это работает? Препарируем Django: Модели Создадим класс с метаклассом MethodCounterBase class SomeClass(object): __metaclass__ = MethodCounterBase def method1(self): pass def method2(self): pass

Slide 21

Slide 21 text

Метаклассы - как это работает? Препарируем Django: Модели Ну и проверим результат >>> p = SomeClass() >>> p.method1() >>> print p._method1_calls_counter 1 >>> p.method1() >>> print p._method1_calls_counter 2 >>> p.method2() >>> print p._method2_calls_counter 1

Slide 22

Slide 22 text

Что именно происходит в ModelBase Препарируем Django: Модели Сначала формируется пустой класс (без атрибутов) super_new = super(ModelBase, cls).__new__ ... new_class = super_new(cls, name, bases, {'__module__': module}) Получаем класс Meta из декларации модели или унаследованный attr_meta = attrs.pop('Meta', None) abstract = getattr(attr_meta, 'abstract', False) if not attr_meta: meta = getattr(new_class, 'Meta', None) else: meta = attr_meta

Slide 23

Slide 23 text

Что именно происходит в ModelBase Препарируем Django: Модели На основе вычисленного meta в модель добавляется атрибут _meta new_class.add_to_class('_meta', Options(meta, **kwargs)) Потом будет еще несколько проверок на наследование свойств, от родительских моделей.

Slide 24

Slide 24 text

Что именно происходит в ModelBase Препарируем Django: Модели Еще одна проверка, связанная с наследованием if getattr(new_class, '_default_manager', None): if not is_proxy: new_class._default_manager = None new_class._base_manager = None else: new_class._default_manager = new_class._default_manager._copy_to_model(new_class) new_class._base_manager = new_class._base_manager._copy_to_model(new_class)

Slide 25

Slide 25 text

Что именно происходит в ModelBase Препарируем Django: Модели Теперь классу присваиваются все атрибуты, указанные в коде модели for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) def add_to_class(cls, name, value): if hasattr(value, 'contribute_to_class'): value.contribute_to_class(cls, name) else: setattr(cls, name, value)

Slide 26

Slide 26 text

Что именно происходит в ModelBase Препарируем Django: Модели Вот тут происходит магия с полями моделей #Базовая имплементация contribute_to_class в models.fields.Field def contribute_to_class(self, cls, name): self.set_attributes_from_name(name) self.model = cls cls._meta.add_field(self) if self.choices: setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self))

Slide 27

Slide 27 text

Что именно происходит в ModelBase Препарируем Django: Модели Вот тут происходит магия с полями моделей #Базовая имплементация contribute_to_class в models.fields.Field def contribute_to_class(self, cls, name): self.set_attributes_from_name(name) self.model = cls cls._meta.add_field(self) if self.choices: setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self)) Поля добавляются не в класс модели, а во вложенный класс Options и доступны через cls._meta.fields

Slide 28

Slide 28 text

Что именно происходит в ModelBase Препарируем Django: Модели Последний шаг - если модель не абстрактная, она регистрируется в пуле моделей и возвращается уже из этого пула new_class._prepare() register_models(new_class._meta.app_label, new_class) return get_model(new_class._meta.app_label, name, seed_cache=False, only_installed=False) Абстрактная же, нигде не регистрируется if abstract: attr_meta.abstract = False new_class.Meta = attr_meta return new_class

Slide 29

Slide 29 text

Понятно где поля, а где значения? Препарируем Django: Модели Но так и не понятно главное - как же возвращаются и устанавливается значения полей - строки, числа и связанные модели Значения индивидуальны для каждого экземпляра модели (объекта) Значит, присвоение происходит где-то при работе с экземпляром Заглянем в метод __init__, класса Model

Slide 30

Slide 30 text

Как создается экземпляр модели Препарируем Django: Модели Заглянем в метод __init__, класса Model def __init__(self, *args, **kwargs): fields_iter = iter(self._meta.fields) ... if not kwargs: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) else: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) kwargs.pop(field.name, None) if isinstance(field.rel, ManyToOneRel): kwargs.pop(field.attname, None)

Slide 31

Slide 31 text

Как создается экземпляр модели Препарируем Django: Модели Заглянем в метод __init__, класса Model def __init__(self, *args, **kwargs): fields_iter = iter(self._meta.fields) ... if not kwargs: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) else: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) kwargs.pop(field.name, None) if isinstance(field.rel, ManyToOneRel): kwargs.pop(field.attname, None) MyModel(field1=1, field2="text") Этот код ищет в self._meta.fields поля field1 и field2, и устанавливает экземпляру аттрибуты self.field1 = 1 self.field2 = "text"

Slide 32

Slide 32 text

Как создается экземпляр модели Препарируем Django: Модели Второй проход по списку полей ... for field in fields_iter: ... val = field.get_default() ... if is_related_object: setattr(self, field.name, rel_obj) else: setattr(self, field.attname, val)

Slide 33

Slide 33 text

Как создается экземпляр модели Препарируем Django: Модели obj = MyModel.objects.get(id=1) 1. Строится SQL-запрос 2. Получаются и обрабатываются данные из БД 3. Вызывается MyModel(field_1=DATA_FROM_DB_1, field_2=DATA_FROM_DB_2, ..., field_N=field1=DATA_FROM_DB_N)

Slide 34

Slide 34 text

Что мы пропустили Препарируем Django: Модели Что мы пропустили • Как решаются переопределения и наследования значений Meta • Как распределяются простые и related поля при создании класса модели • Как вообще обрабатываются related-поля • Как создаются менеджеры для моделей

Slide 35

Slide 35 text

Где копаться дальше Препарируем Django: Модели Немного ссылок на исходники https://github.com/django/django/blob/stable/1.4.x/django/db/models/base.py#L28 https://github.com/django/django/blob/stable/1.4.x/django/db/models/base.py#L274 ModelBase Model https://github.com/django/django/blob/stable/1.4.x/django/db/models/options.py#L22 Options (Meta) http://docs.python.org/2/reference/datamodel.html#customizing-class-creation Customizing class creation

Slide 36

Slide 36 text

Где копаться дальше Препарируем Django: Модели PS: В python 3.x изменился синтаксис указания метаклассов __metaclass__ = Meta -> class X(metaclass=Meta)

Slide 37

Slide 37 text

It’s all about music business ВОПОСЫ? – [email protected] FACEBOOK - facebook.com/pyhoster Спасибо!