Pull to refresh

Именованные кортежи из выборок

Reading time5 min
Views11K
В Django для ускорения запросов, возвращающих большое количество данных, существуют методы QuerySetvalues() и 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())
>>> = Point(x=11, y=22)   # создание с помощью именованных
>>> p2 = Point(1122)      # ... или позиционных параметров
>>> p1 == p2
True
>>> p[0+ p[1]             # индексация
33
>>> x, y = p                # ... и распаковка как для обычных кортежей
>>> x, y
(11, 22)
>>> p.+ p.y               # доступ к элементам через атрибуты
33
>>> Point._make([1122])   # создание из итерируемого объекта
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().
Tags:
Hubs:
Total votes 50: ↑45 and ↓5+40
Comments34

Articles