2011-08-02 326 views
12

我有一個奇怪的問題,使用itertools.groupby來分組查詢集的元素。我有一個模型Resourceitertools.groupby在Django模板

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> 

這使得爲:

select multiple

我想以某種方式subiterators被遍歷已,但我不確定這會如何發生。

(使用python 2.7.1,Django 1.3)。

(編輯:如果有人讀取此,我建議使用內置regroup template tag而不是使用groupby

回答

16

我認爲你是對的。我不明白爲什麼,但它在我看來像你的迭代器正在迭代。用代碼解釋更容易:

>>> 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如何處理迭代器,但基於此,我的直覺是它將它們緩存爲內部列表。通過這樣做,你至少可以部分地證實這種直覺,但是可以獲得更多的資源。如果顯示的唯一資源是最後一個資源,那麼幾乎肯定會在某處出現上述問題。

+2

感謝您的調查;我用~10資源試了一下,每個組最多隻有一個資源 - 我用'(t,list(r))爲上下文填充t,r在groupby(...)中進行了修復' –

+0

是的,迭代器正在進行預迭代,Django將迭代器轉換爲列表而無需遍歷分組項。我在一個單獨的答案中添加了解釋。 –

14

Django的模板想知道使用{% for %}循環的東西的長度,但生成器沒有長度。

因此,Django決定在迭代之前將其轉換爲列表,以便它可以訪問列表。

這破壞了使用itertools.groupby創建的發電機。如果您不遍歷每個組,則會丟失內容。這裏是an example from Django core developer 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] 

當然不過,你不再有使用發電機的優勢,如果這是你的問題。

從轉換成列表

解決這個問題的另一種方法是把它包在提供__len__方法(如果你知道的長度將是什麼)的對象預防的Django:

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

    def __len__(self): 
     return 2 

Django將能夠使用len()獲得長度,並且不需要將您的生成器轉換爲列表。 Django做到這一點很不幸。我很幸運,我可以使用這種解決方法,因爲我已經在使用這樣一個對象,並且知道長度總是如此。

+0

不錯,很高興有人知道Django的知識。 – senderle