libreria groupby from example espaƱol python django group-by django-templates itertools

python - from - itertools.groupby en una plantilla django



python 3.7 itertools (2)

Tengo un problema extraño al usar itertools.groupby para agrupar los elementos de un conjunto de preguntas. Tengo un Resource modelo:

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

Tengo un par de recursos en mi base de datos 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''

Entonces, si agrupo por tipo, naturalmente obtengo dos tuplas:

>>> 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

Ahora tengo la misma lógica en mi opinión:

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

Pero cuando repito esto en mi plantilla, faltan algunos recursos:

<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>

Esto se traduce como:

Estoy pensando que de alguna manera los iteradores ya se han repetido, pero no estoy seguro de cómo podría suceder.

(Usando Python 2.7.1, Django 1.3).

(EDITAR: si alguien lee esto, recomendaría usar la etiqueta de plantilla de regroup incorporada en lugar de usar groupby ).


Creo que tienes razón No entiendo por qué, pero me parece que su iterador groupby está siendo iterado. Es más fácil de explicar con código:

>>> 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])]

Hasta aquí todo bien. Pero, ¿qué ocurre cuando intentamos almacenar los contenidos del iterador en una lista?

>>> 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>)]

Seguramente acabamos de guardar en caché los resultados, y los iteradores siguen siendo buenos. ¿Derecha? Incorrecto.

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

En el proceso de adquirir las claves, los grupos también se repiten. Así que acabamos de guardar en caché las claves y deshacernos de los grupos, guardar el último elemento.

No sé cómo maneja django los iteradores, pero en base a esto, mi corazonada es que los almacena en caché como listas internamente. Podría al menos confirmar parcialmente esta intuición haciendo lo anterior, pero con más recursos. Si el único recurso que se muestra es el último, entonces es casi seguro que tienes el problema anterior en alguna parte.


Las plantillas de Django quieren saber la longitud de las cosas que se repiten usando {% for %} , pero los generadores no tienen una longitud.

Entonces Django decide convertirlo a una lista antes de iterar, para que tenga acceso a una lista.

Esto rompe generadores creados usando itertools.groupby . Si no itera a través de cada grupo, pierde los contenidos. Aquí hay un ejemplo del desarrollador principal de Django Alex Gaynor , primero el grupo normal por:

>>> 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]]

Esto es lo que hace Django; convierte el generador a una lista:

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

Hay dos formas de evitar esto: convertir a una lista antes de que Django lo haga o evitar que Django lo haga.

Convertir en una lista usted mismo

Como se muestra arriba:

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

Pero, por supuesto, ya no tiene las ventajas de usar un generador, si esto es un problema para usted.

Impedir que Django se convierta en una lista

A la inversa, esto es envolverlo en un objeto que proporciona un método __len__ (si sabes cuál será la longitud):

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

Django podrá obtener la longitud usando len() y no necesitará convertir su generador en una lista. Es desafortunado que Django haga esto. Tuve la suerte de poder utilizar esta solución, ya que ya estaba usando un objeto así y sabía cuál sería la longitud siempre.