Как стать автором
Обновить

Комментарии 34

Item.objects.all().only('name')

возвращает модель с Deferred Attributes. и никаких велосипедов
Создание экземпляров моделей относительно медленно, а когда строк много заметно тормозит.
*queryset с моделями, конечно
Не могли бы вы объяснить, как работают deferred attributes? Не совсем понятна официальная документация была.

Я правильно понимаю, что они вытягивают поле как values по SELECT fields, а при обращении к другим элементам, которых нет в списке делается еще запрос?
Да.
In [26]: o = pa.model.objects.all().only('name')[0]

In [27]: o
Out[27]: <Product_Deferred_list_of_deferred_fields: someproduct>

In [28]: dict(o.__class__.__dict__)
Out[28]:
{...
'ops': <db.models.query_utils.DeferredAttribute object at 0x7eb6150c>,
...}

In [30]: o.ops
Out[30]: u'лалала'

In [31]: connection.queries[-2:]
Out[31]:
[{'sql': u'SELECT "product"."id", "product"."name" FROM "product" LIMIT 1',
'time': '0.000'},
{'sql': u'SELECT "product"."id", "product"."ops" FROM "product" WHERE "ipc_product"."id" = 1 ',
'time': '0.000'}]



DeferredAttribute — обычный дескриптор новосозданного (см db.models.deferred_class_factory) класса внутри QuerySet, который реализует нужное поведение внутри __get__.

Да, это не так быстро, как namedtuples (а изначально было создано для исключения _больших_ полей типа text из выборки, а не включения маленьких по одному) — но в случае особого желания доступа к элементам по именам делая большую выборку из большой базы, имхо, легче будет просто распаковать кортеж из values_list() внутри тела цикла.
Тормозит не только выборка большого количества данных из базы, но и само создание большого количества моделей. Т.е. если в результате много строк (не колонок), то рекомендуется использовать values(), либо values_list().
Point = namedtuple('Point', 'x y'.split())

Простите, что за индусский код? Почему не

Point = namedtuple('Point', ('x', 'y'))
Причём тут индусский код? Нормальная идиома, особенно, для знакомых с перлом.
Это консольная сессия в конце концов, меньше знаков препинания — хорошо.
Для питона это ненормально.
Ну раз уж совсем питон, то я бы делал (относительно примера):
sum(i * p for i,p in Item.objects.all().values_list('item', 'price'))
— и о namedtuples бы даже не подумал :-P
Более того, в документации английским по белому указан вариант

namedtuple(«name», «a b»)
немного не так )

collections.namedtuple(typename, field_names, verbose=False, rename=False)

The field_names are a single string with each fieldname separated by whitespace and/or commas, for example 'x y' or 'x, y'. Alternatively, field_names can be a sequence of strings such as ['x', 'y'].
namedtuple позволяет передавать в качестве имён полей строку с именами, разделёнными пробелами и запятыми — и вот это действительно говнокод, да ещё противоречащий духу питона, но тем не менее… в стандартную библиотеку попал.
Оптимизировать нужно знать что, есть ли какие-то замеры насколько «меньше, быстрее» и т.д.? Может такая оптимизация со сложными новыми туплами и не дает много.
Даже если она не даёт ничего, она сделает мой код красивее. Да и даёт, особенно если в кеш складывать, туплы и пиклить быстрее и памяти меньше занимают.
Имхо с простыми кортежами всё же красивее.

qs = Item.objects.filter(...).values_list('title', 'amount', 'price')
for title, amount, price in qs:
    print title, amount
    total += amount * price
По-моему начались дни джанги на Хабре.
Это хорошо.
Эх, Django больше не илитне…
Буду переживать теперь.
переходите на фласк. только хардкор.
Мне кажется, это определенно должно быть встроено в фреймворк, поэтому создал feature request в багтрекере джанги с этой штукой.

Спасибо, буду использовать вместо values_list и values. Оверхед тут совсем маленький.
Вообще-то, я написал письмецо в django-developers, пока решили оставить это в дикой природе. Посмотреть как пойдёт.
Сомневаюсь что прокатит.
Это добавит 2.6 в ограничения.
С таким же успехом можно использовать решение из поста.
Я, как правило, делаю так:

qs = Item.objects.filter(...).values_list('title', 'amount', 'price')
for title, amount, price in qs:
    print title, amount
    total += amount * price


По-моему, это вполне красиво и читабельно. И никакие велосипеды не нужны.

В случае с

qs = Item.objects.filter(...).namedtuples('title', 'amount', 'price')
for item in qs:
    print item.title, item.amount
    total += item.amount * item.price


может возникнуть путаница, потому что на первый взгляд создаётся впечатление, что item — это полноценная модель.

И ещё исправьте, пожалуйста. Вместо Items.objects...., должно быть Item.objects....
А если полей 10? или нужно передать список результатов в шаблон? Ни одно решение не универсально.
Хм… Поупражнялся с вашим кодом, и правда удобно и оверхеда практически нет.

In [8]: %timeit Product.objects.all().values_list('title', 'unit_price')
10000 loops, best of 3: 160 us per loop

In [9]: %timeit Product.objects.all().namedtuples('title', 'unit_price')
10000 loops, best of 3: 162 us per loop

In [10]: Product.objects.count()
Out[10]: 1867
Хм… Я тоже для этого .only('name1', 'name2') использовал. По моему, описываемую вами проблему покрывает полностью но при этом более гибкое и прозрачное.
В отладке .name очень неудобен. Values / values lists показывает явно информацию.
Спасибо, отличное решение!
А как бы сделть так, чтобы получать не список, а словарь, где ключом являлось бы значение заданного поля?

Типа:
votes = Vote.objects.filter(object_id = 1, content_type=1).namedtuples('value', 'vote_time', key='user_id')
user_vote = votes.get(user.pk)


Я часто использую такой подход для оптимизации выборок (вместо join-ов):
1. Получаем список объектов
2. Пробегаемся по списку и набираем необходимые id-шники связанных объектов
3. Получаем связанные объекты используя SELECT… IN (id, ...)
4. Прицепляем связанные объекты к исходному списку (либо во view, либо непосредственно в темплейтах)

Соответственно в 3-ем пункте мне нужны записи сгруппированные по определённому полю. Я до сих пор не выработал удобный подход для подобных манипуляций. Может быть вы что-то подскажете?
У джанговского QuerySet-а есть стандартный метод in_bulk(). Он делает именно то, что вам нужно. Правда, будет ли он работать с namedtuples неизвестно.

Но всегда можно использовать:
objs_by_pk = dict((obj.pk, obj) for obj in queryset)
in_bulk я использую когда связка происходит по первичному ключу, но это не всегда так. Пример из жизни: фильм можно оценить и/или написать не него отзыв. На странице фильма нужно вывести список лучших отзывов вместе с оценками тех, кто их написал. Лучшие отзывы определяются голосованием, а значит нужно выбрать ещё и голоса текущего пользователя за отзывы. Как я это делаю:

  • Получаю несколько лучших отзывов
  • Выбираю из полученного списка первичные ключи и id пользователей
  • Получаю голоса выбранных пользователей и добавляю их в список отзывов. Тут связка отзыв.user_id→оценка.user_id
  • Получаю и связываю голоса текущего пользователя за отзывы. Связка отзыв.id→оценка.object_id
  • Ну и, заодно, выбираю самих авторов отзывов, т.к. INNER JOIN генерируемый select_related() почти всегда даёт filesort в MySQL. Связка отзыв.user_id→пользователь.id. И только в этом случае мне подходит in_bulk()

С именнованными кортежами я поэкспериментировал. Модифицировать ваш код, так чтобы он мог группировать по ключу у меня не вышло, но, самым неприятным оказался тот факт, что в namedtuple нельзя добавить новые поля…
> Модифицировать ваш код, так чтобы он мог группировать по ключу у меня не вышло
Это было бы неправильно с точки зрения архитектуры. Если часто нужно группировать, добавьте свой метод на queryset, который будет выдавать словарь по нужному ключу. Или не метод, шорткат.

> самым неприятным оказался тот факт, что в namedtuple нельзя добавить новые поля
Это, всё-таки, tuple. И зачем?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации