В Django для ускорения запросов, возвращающих большое количество данных, существуют методы
Для тех кто не сталкивался и для тех кого пока не убедил, приведу кусочек кода, иллюстрирующий текущее положение вещей:
Варианты со словарями и кортежами не так симпатичны как с моделями, но работают быстрее и требуют меньше памяти. Именованные кортежи позволяют обращаться к своим полям по атрибуту, т.е. с ними наш код будет почти не отличаться от кода для моделей. Отсюда сопутствующий бонус — во многих случаях мы сможем переходить от моделей к кортежам и обратно не меняя кода, а там где его придётся поменять, изменения будут минимальны. Кроме того, кортежи хранят имена полей в классе, а не каждом объекте как словари, поэтому они занимают меньше памяти, что приятно само по себе и будет иметь ещё большее значение если вам придёт в голову складывать результаты запросов в кеш.
Именованные кортежи появились в питоне 2.6 и представляют собой кортежи, в которых доступ к элементам возможен по заданным атрибутам. Небольшой кусочек кода для демонстрации:
Осталось создать потомок
И, для удобства использования припишем метод к
После этого код, приведённый в начале статьи, для моделей, словарей и кортежей можно будет записать как:
Забавно, что получившийся класс выполняет функции обоих имеющихся ускорителей
P.S. Код класса вместе с патчем можно стянуть отсюда gist.github.com/876324
P.P.S. Порядок полей в кортеже может не соответствовать порядку аргументов
QuerySet
-а values()
и values_list()
. Первый вместо моделей возвращает словари, второй кортежи. Работать и с теми, и с другими не так удобно как с экземплярами моделей, дескать, платите ребята за скорость удобством. А я вот не хочу, и благодаря именнованым кортежам из стандартного модуля collections
, не буду.Для тех кто не сталкивался и для тех кого пока не убедил, приведу кусочек кода, иллюстрирующий текущее положение вещей:
qs = Item.objects.filter(...)
for item in qs:
print item.title, item.amount
total += item.amount * item.price
# используем словари
qs = Item.objects.filter(...).values('title', 'amount', 'price')
for item in qs:
print item['title'], item['amount']
total += item['amount'] * item['price']
# используем кортежи
qs = Item.objects.filter(...).values_list('title', 'amount', 'price')
for item in qs:
print item[0], item[1]
total += item[1] * item[2]
Варианты со словарями и кортежами не так симпатичны как с моделями, но работают быстрее и требуют меньше памяти. Именованные кортежи позволяют обращаться к своим полям по атрибуту, т.е. с ними наш код будет почти не отличаться от кода для моделей. Отсюда сопутствующий бонус — во многих случаях мы сможем переходить от моделей к кортежам и обратно не меняя кода, а там где его придётся поменять, изменения будут минимальны. Кроме того, кортежи хранят имена полей в классе, а не каждом объекте как словари, поэтому они занимают меньше памяти, что приятно само по себе и будет иметь ещё большее значение если вам придёт в голову складывать результаты запросов в кеш.
Что же такое именованные кортежи?
Именованные кортежи появились в питоне 2.6 и представляют собой кортежи, в которых доступ к элементам возможен по заданным атрибутам. Небольшой кусочек кода для демонстрации:
>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y'.split())
>>> p = Point(x=11, y=22) # создание с помощью именованных
>>> p2 = Point(11, 22) # ... или позиционных параметров
>>> p1 == p2
True
>>> p[0] + p[1] # индексация
33
>>> x, y = p # ... и распаковка как для обычных кортежей
>>> x, y
(11, 22)
>>> p.x + p.y # доступ к элементам через атрибуты
33
>>> Point._make([11, 22]) # создание из итерируемого объекта
Point(x=11, y=22)
NamedTuplesQuerySet
Осталось создать потомок
QuerySet
, который будет выдавать именованные кортежи. Благодаря тому, что вся логика запроса с обозначенными полями реализована в ValuesQuerySet
(возвращается из QuerySet.values()
), нам остаётся только обработать результат перед выдачей:from itertools import imap
from collections import namedtuple
from django.db.models.query import ValuesQuerySet
class NamedTuplesQuerySet(ValuesQuerySet):
def iterator(self):
# собираем список имён полей
extra_names = self.query.extra_select.keys()
field_names = self.field_names
aggregate_names = self.query.aggregate_select.keys()
names = extra_names + field_names + aggregate_names
# создаём класс кортежа
tuple_cls = namedtuple('%sTuple' % self.model.__name__, names)
results_iter = self.query.get_compiler(self.db).results_iter()
# заворачиваем каждую строку в наш именованный кортеж
return imap(tuple_cls._make, results_iter)
И, для удобства использования припишем метод к
QuerySet
:from django.db.models.query import QuerySet
def namedtuples(self, *fields):
return self._clone(klass=NamedTuplesQuerySet, setup=True, _fields=fields)
QuerySet.namedtuples = namedtuples
После этого код, приведённый в начале статьи, для моделей, словарей и кортежей можно будет записать как:
qs = Item.objects.filter(...).namedtuples('title', 'amount', 'price')
for item in qs:
print item.title, item.amount
total += item.amount * item.price
Забавно, что получившийся класс выполняет функции обоих имеющихся ускорителей
values()
и values_list()
. Причём делает это либо лучше, либо почти также и при этом порождает более красивый код. Может, когда-нибудь это приведёт к их замене. P.S. Код класса вместе с патчем можно стянуть отсюда gist.github.com/876324
P.P.S. Порядок полей в кортеже может не соответствовать порядку аргументов
nametuples()
, забил на это ради скорости. Реализацию переупорядочивания полей, если кому надо, можно взять из ValuesListQuerySet.iterator()
.