itertools.groupby в шаблоне django

У меня возникла странная проблема с использованием itertools.groupby для группировки элементов itertools.groupby . У меня есть модель Resource :

 from django.db import models TYPE_CHOICES = ( ('event', 'Event Room'), ('meet', 'Meeting Room'), # etc ) class Resource(models.Model): name = models.CharField(max_length=30) type = models.CharField(max_length=5, choices=TYPE_CHOICES) # other stuff 

У меня есть несколько ресурсов в моей базе данных sqlite:

 >>> from myapp.models import Resource >>> r = Resource.objects.all() >>> len(r) 3 >>> r[0].type u'event' >>> r[1].type u'meet' >>> r[2].type u'meet' 

Поэтому, если я группируюсь по типу, я, естественно, получаю два кортежа:

 >>> from itertools import groupby >>> g = groupby(r, lambda resource: resource.type) >>> for type, resources in g: ... print type ... for resource in resources: ... print '\t%s' % resource event resourcex meet resourcey resourcez 

Теперь у меня есть та же логика, на мой взгляд:

 class DayView(DayArchiveView): def get_context_data(self, *args, **kwargs): context = super(DayView, self).get_context_data(*args, **kwargs) types = dict(TYPE_CHOICES) context['resource_list'] = groupby(Resource.objects.all(), lambda r: types[r.type]) return context 

Но когда я повторяю это в своем шаблоне, некоторые ресурсы отсутствуют:

 <select multiple="multiple" name="resources"> {% for type, resources in resource_list %} <option disabled="disabled">{{ type }}</option> {% for resource in resources %} <option value="{{ resource.id }}">{{ resource.name }}</option> {% endfor %} {% endfor %} </select> 

Это означает, что:

выбрать несколько

Я как-то думаю, что субтитры уже повторяются, но я не уверен, как это может произойти.

(Использование python 2.7.1, Django 1.3).

(EDIT: если кто-нибудь прочтет это, я бы рекомендовал использовать встроенный тег шаблона regroup вместо использования groupby .)

2 Solutions collect form web for “itertools.groupby в шаблоне django”

Я думаю, что ты прав. Я не понимаю, почему, но мне кажется, что ваша groupby итератором проходит предварительную groupby . Это проще объяснить с помощью кода:

 >>> even_odd_key = lambda x: x % 2 >>> evens_odds = sorted(range(10), key=even_odd_key) >>> evens_odds_grouped = itertools.groupby(evens_odds, key=even_odd_key) >>> [(k, list(g)) for k, g in evens_odds_grouped] [(0, [0, 2, 4, 6, 8]), (1, [1, 3, 5, 7, 9])] 

Все идет нормально. Но что происходит, когда мы пытаемся сохранить содержимое итератора в списке?

 >>> evens_odds_grouped = itertools.groupby(evens_odds, key=even_odd_key) >>> groups = [(k, g) for k, g in evens_odds_grouped] >>> groups [(0, <itertools._grouper object at 0x1004d7110>), (1, <itertools._grouper object at 0x1004ccbd0>)] 

Конечно, мы просто кэшировали результаты, и итераторы по-прежнему хороши. Правильно? Неправильно.

 >>> [(k, list(g)) for k, g in groups] [(0, []), (1, [9])] 

В процессе приобретения ключей группы также повторяются. Таким образом, мы действительно просто кэшировали ключи и выбрасывали группы, сохраняя последний элемент.

Я не знаю, как django обрабатывает итераторы, но, исходя из этого, я подозреваю, что он кэширует их как списки внутри. Вы могли бы хотя бы частично подтвердить эту интуицию, выполнив вышеуказанное, но с большим количеством ресурсов. Если единственным отображаемым ресурсом является последний, то вы почти наверняка имеете проблему выше.

Шаблоны Django хотят знать длину вещей, которые зацикливаются, используя {% for %} , но генераторы не имеют длины.

Поэтому Django решает преобразовать его в список перед итерацией, чтобы он имел доступ к списку.

Это разбивает генераторы, созданные с помощью itertools.groupby . Если вы не перебираете каждую группу, вы теряете содержимое. Вот пример из основного разработчика Django Alex Gaynor , сначала нормальный groupby:

 >>> groups = itertools.groupby(range(10), lambda x: x < 5) >>> print [list(items) for g, items in groups] [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]] 

Вот что делает Django; он преобразует генератор в список:

 >>> groups = itertools.groupby(range(10), lambda x: x < 5) >>> groups = list(groups) >>> print [list(items) for g, items in groups] [[], [9]] 

Есть два способа обойти это: конвертировать в список до того, как Django делает или запрещает Django делать это.

Преобразование в список самостоятельно

Как показано выше:

 [(grouper, list(values)) for grouper, values in my_groupby_generator] 

Но, конечно, у вас больше нет преимуществ использования генератора, если это проблема для вас.

Предотвращение перехода Django в список

Другой способ – обернуть его в объект, который предоставляет метод __len__ (если вы знаете, какая длина будет):

 class MyGroupedItems(object): def __iter__(self): return itertools.groupby(range(10), lambda x: x < 5) def __len__(self): return 2 

Django сможет получить длину, используя len() и вам не нужно будет преобразовывать ваш генератор в список. К сожалению, Джанго это делает. Мне повезло, что я мог бы использовать это обходное решение, поскольку я уже использовал такой объект и знал, что длина всегда будет.

  • Есть ли эквивалент Python's itertools для Java?
  • Перечислите (элемент, другие) в списке
  • Python itertools.combinations: как получить индексы комбинированных номеров
  • Группируйте и суммируйте значения списка словарей в Python
  • Все возможные способы чередования двух строк
  • Сумма продуктов пар в списке
  • Заменить список списка «сокращенным» списком списка при сохранении порядка
  • python itertools round robin без дублирования
  • Python - лучший язык программирования в мире.