Ism*_*awi 16 python django group-by django-templates python-itertools
我有一个奇怪的问题,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
Run Code Online (Sandbox Code Playgroud)
我的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'
Run Code Online (Sandbox Code Playgroud)
因此,如果我按类型分组,我自然会得到两个元组:
>>> 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
Run Code Online (Sandbox Code Playgroud)
现在我的观点中有相同的逻辑:
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
Run Code Online (Sandbox Code Playgroud)
但是当我在模板中迭代这个时,会丢失一些资源:
<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>
Run Code Online (Sandbox Code Playgroud)
这呈现为:

我在想某些subiterator已经被迭代了,但我不确定这是怎么发生的.
(使用python 2.7.1,Django 1.3).
(编辑:如果有人读到这个,我建议使用内置regroup模板标签而不是使用groupby.)
Wil*_*rdy 21
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]]
Run Code Online (Sandbox Code Playgroud)
这是Django的作用; 它将生成器转换为列表:
>>> groups = itertools.groupby(range(10), lambda x: x < 5)
>>> groups = list(groups)
>>> print [list(items) for g, items in groups]
[[], [9]]
Run Code Online (Sandbox Code Playgroud)
有两种方法:在Django之前转换为列表或阻止Django这样做.
如上图所示:
[(grouper, list(values)) for grouper, values in my_groupby_generator]
Run Code Online (Sandbox Code Playgroud)
但是,当然,如果这对您来说是一个问题,您将不再拥有使用发电机的优势.
另一种方法是将它包装在一个提供__len__方法的对象中(如果你知道长度是多少):
class MyGroupedItems(object):
def __iter__(self):
return itertools.groupby(range(10), lambda x: x < 5)
def __len__(self):
return 2
Run Code Online (Sandbox Code Playgroud)
Django将能够使用长度,len()并且不需要将您的生成器转换为列表.不幸的是Django这样做了.我很幸运,我可以使用这种解决方法,因为我已经使用了这样一个对象并且知道长度总是如此.
sen*_*rle 18
我认为你是对的.我不明白为什么,但它看起来像你的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])]
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.但是当我们尝试将迭代器的内容存储在列表中时会发生什么?
>>> 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>)]
Run Code Online (Sandbox Code Playgroud)
当然我们刚刚缓存了结果,迭代器仍然很好.对?错误.
>>> [(k, list(g)) for k, g in groups]
[(0, []), (1, [9])]
Run Code Online (Sandbox Code Playgroud)
在获取密钥的过程中,组也被迭代.所以我们真的只是缓存了密钥并抛弃了组,保存最后一项.
我不知道django如何处理迭代器,但基于此,我的预感是它在内部将它们作为列表进行缓存.你可以通过以上方式至少部分地证实这种直觉,但需要更多的资源.如果显示的唯一资源是最后一个,那么您几乎肯定会遇到上述问题.
| 归档时间: |
|
| 查看次数: |
3277 次 |
| 最近记录: |