В Django для ускорения запросов, возвращающих большое количество данных, существуют методы 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 и представляют собой кортежи, в которых доступ к элементам возможен по заданным атрибутам. Небольшой кусочек кода для демонстрации:
| |
NamedTuplesQuerySet
Осталось создать потомок QuerySet, который будет выдавать именованные кортежи. Благодаря тому, что вся логика запроса с обозначенными полями реализована в ValuesQuerySet (возвращается из QuerySet.values()), нам остаётся только обработать результат перед выдачей:
| |
И, для удобства использования припишем метод к QuerySet:
| |
После этого код, приведённый в начале статьи, для моделей, словарей и кортежей можно будет записать как:
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().
Comments also at Хабр