Введение
Django, аналогично другим фреймворкам, построен на языке программирования, в нашем случае, - это Python. Множество новичков в Django также являются новичками и в Python. Синтаксис языка Python и мощные возможности Django могут представить данный фреймворк как некий метаязык, что на самом деле не так.
Понимание возможностей Django начинается со знания того, что Django - это Python. Она подобна любому из ваших приложений. Всё, что можно сделать в Python, можно сделать и в Django.
Это также означает, что Django приложения имеют доступ не только ко всей стандарной библиотеке языка Python, но и к неограниченной коллекции сторонних библиотек и утилит. Интерфейсы к некоторым таким библиотеками представлены в самой Django, а существующий код и документация достаточны для их использования.
Как Python создаёт классы
Когда интерпретатор достигает определения класса, он читает его содержимое как и любой другой код. Затем Python создаёт новое именованное пространство для класса и выполняет его код, записывая любые назначенные переменные в это именованное пространство. Определения классов обычно содержат переменные, методы и другие классы, всё это принадлежит именованному пространству класса.
После окончания обработки содержимого определения класса Python получает объект класса, который размещён в именованном пространстве, в котором он был определён (обычно это глобальное именованное пространство модуля). Далее этот объект передаётся куда-нибудь или вызывается для создания экземпляров данного класса.
>>> class DefinedClass(object):
... print 'Загрузка DefinedClass...',
... tmp = 'test'
... print 'завершена'
...
Загрузка DefinedClass... завершена
>>> DefinedClass
<class '__main__.DefinedClass'>
>>> DefinedClass.tmp
'test'
Из примера видно, что код выполняется при определении класса, все назначенные переменные отображаются как атрибуты класса, после его инициализации.
Создание класса на лету
Процесс, описанный выше, используется для каждого класса, который был определён в исходном коде. Но способ, которым Python это реализует, предоставляет возможность сделать нечто более интересное. Неявно все данные о декларируемом классе передаются во встроенный объект type, который обеспечивает создание соответствующего объекта для класса. Это выполняется автоматически для каждого класса, немедленно после окончания обработки содержимого определения класса.
Конструктор type принимает три аргумента, которые представляют определение целого класса.
- name - имя класса, в виде строки;
- bases - кортеж, возможно пустой, классов в цепочке наследования;
- attrs - словарь для именованного пространства класса.
Подобно любому объекту языка Python, новый объект type может быть создан в любое время в любом блоке кода. Это означает, что ваш код может создавать новые классы, базируясь на данных, которые были собраны во время выполнения программы. Следующий код демонстрирует способ определения класса на лету:
>>> DynaClass = type('DynaClass', (object,), {'tmp': test'})
>>> DynaClass
<class '__main__.DynaClass'>
>>> DynaClass.tmp
'test'
Метаклассы меняют всё
Очевидно, что type является "метаклассом". Метакласс - это класс, который создаёт другие классы. В сущности, метапрограммирование создаёт или модифицирует код во время выполнения программы, а не во время её написания. Python предоставляет возможность управлять данным процессом, разрешая написать собственный метакласс для выполнения этой работы.
Если определение класса определяет отдельный класс для своего атрибута __metaclass__, то этот метакласс будет использован при создании класса, вместо встроенного объекта type. Это позволяет вашему коду читать, изменять и даже полностью заменять декларируемый класс. Технически, атрибут __metaclass__ может быть назначен любому вызываемому объекту языка Python, но большинство метаклассов унаследованы от type. Метакласс принимает новый класс в качестве первого аргумента и предоставляет доступ к объекту класса.
Пример:
>>> class MetaClass(type):
... def __init__(cls, name, bases, attrs):
... print 'Определяем %s' % cls
... print 'Имя: %s' % name
... print 'Наследование: %s' % (bases,)
... print 'Атрибуты:'
... for (name, value) in attrs.items():
... print ' %s: %r' % (name, value)
...
>>> class RealClass(object)
... __metaclass__ = MetaClass
... tmp = 'test'
...
Определяем <class '__main__.RealClass'>
Имя: RealClass
Наследование: (<type 'object'>,)
Атрибуты:
__module__: '__main__'
__metaclass__: <class '__main__.MetaClass'>
tmp: 'test'
>>> RealClass
<class '__main__.RealClass'>
Следует отметить, что метакласс нигде не создавался. Простое действие по созданию класса запустило выполнение метакласса. Также обратите внимание на __module__ в списке атрибутов класса. Этот атрибут есть у всех классов языка Python.
В то время как приведённый выше пример использует метод __init__ для обработки создаваемых классов, существует другой, в некотором роде даже более мощный метод __new__ с другим набором возможностей.
Методу __new__ передаются все аргументы создаваемого класса, следовательно можно полностью изменить работу создаваемого класса. Важно определять метод __new__, вместо __init__, так как при создании класса вызываются оба этих метода. Метод __init__ инициализирует экземпляр класса, а метод __new__ отвечает за само создание класса. Следовательно, если наш метакласс должен влиять на процесс создания класса, следует переопределять метод __new__.
Пример:
>>> class MetaClass(type):
... def __new__(cls, name, bases, attrs):
... new_class = super(MetaClass, cls).__new__(cls, name, bases, attrs)
... # здесь производим необходимые действия
... return new_class
...
Обратите внимание на то, что метод __new__ должен возвращать значение, чего не делает метод __init__.
Использование базового класса с метаклассом
Метаклассы могут быть достаточно полезны, но сама переменная __metaclass__ является деталью реализации, которая не должна быть частью процесса определения класса.
Раз каждый класс обрабатывается метаклассом, они не унаследованы от какого-то конкретного класса. Это означает, что любая дополнительная функциональность, например, общие методы или атрибуты, должна быть предоставлена во время работы метакласса.
Класс языка Python может использовать метакласс для решения этих обоих проблем. Так как наследуемые классы наследуют атрибуты базового класса, то переменная __metaclass__ автоматически появляется во всех унаследованных классах. Это просто и эффективно.
Посмотрим, что происходит при наследовании от класса RealClass:
>>> class SubClass(RealClass):
... pass # обратите внимание, здесь нет метакласса
...
Определяем <class '__main__.SubClass'>
Имя: SubClass
Наследование: (<type '__main__.RealClass'>,)
Атрибуты:
__module__: '__main__'
Отметьте, что в новом классе вся работа метакласса скрыта от программиста. Просто наследуясь от базового класса, новый класс получает всё вкусное без лишних усилий. Django использует это поведение в своей реализации работы с формами, и не только там.