由 itertools.groupby() 生成的迭代器被意外消耗
Posted
技术标签:
【中文标题】由 itertools.groupby() 生成的迭代器被意外消耗【英文标题】:Iterator produced by itertools.groupby() is consumed unexpectedly 【发布时间】:2016-01-06 22:24:25 【问题描述】:我写了一个基于迭代器的小程序来显示多列日历。
在该代码中,我使用itertools.groupby
通过函数group_by_months()
按月对日期进行分组。在那里,我将月份名称和分组日期作为每个月的列表。但是,当我让该函数直接将分组日期作为迭代器(而不是列表)返回时,程序将除最后一列之外的所有日期留空。
我不知道为什么会这样。我使用 groupby 错了吗?谁能帮我找出迭代器被消耗或它的输出被忽略的地方?为什么特别是最后一列“幸存”?
代码如下:
import datetime
from itertools import zip_longest, groupby
def grouper(iterable, n, fillvalue=None):
"""\
copied from the docs:
https://docs.python.org/3.4/library/itertools.html#itertools-recipes
"""
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
def generate_dates(start_date, end_date, step=datetime.timedelta(days=1)):
while start_date < end_date:
yield start_date
start_date += step
def group_by_months(seq):
for k,v in groupby(seq, key=lambda x:x.strftime("%B")):
yield k, v # Why does it only work when list(v) is yielded here?
def group_by_weeks(seq):
yield from groupby(seq, key=lambda x:x.strftime("%2U"))
def format_month(month, dates_of_month):
def format_week(weeknum, dates_of_week):
def format_day(d):
return d.strftime("%3e")
weekdays = d.weekday(): format_day(d) for d in dates_of_week
return "0 7 1 2 3 4 5 6".format(
weeknum, *[weekdays.get(i, " ") for i in range(7)])
yield ":^30".format(month)
weeks = group_by_weeks(dates_of_month)
yield from map(lambda x:format_week(*x), weeks)
start, end = datetime.date(2016,1,1), datetime.date(2017,1,1)
dates = generate_dates(start, end)
months = group_by_months(dates)
formatted_months = map(lambda x: (format_month(*x)), months)
ncolumns = 3
quarters = grouper(formatted_months, ncolumns)
interleaved = map(lambda x: zip_longest(*x, fillvalue=" "*30), quarters)
formatted = map(lambda x: "\n".join(map(" ".join, x)), interleaved)
list(map(print, formatted))
这是失败的输出:
January February March
09 1 2 3 4 5
10 6 7 8 9 10 11 12
11 13 14 15 16 17 18 19
12 20 21 22 23 24 25 26
13 27 28 29 30 31
April May June
22 1 2 3 4
23 5 6 7 8 9 10 11
24 12 13 14 15 16 17 18
25 19 20 21 22 23 24 25
26 26 27 28 29 30
July August September
35 1 2 3
36 4 5 6 7 8 9 10
37 11 12 13 14 15 16 17
38 18 19 20 21 22 23 24
39 25 26 27 28 29 30
October November December
48 1 2 3
49 4 5 6 7 8 9 10
50 11 12 13 14 15 16 17
51 18 19 20 21 22 23 24
52 25 26 27 28 29 30 31
这是预期的输出:
January February March
00 1 2 05 1 2 3 4 5 6 09 1 2 3 4 5
01 3 4 5 6 7 8 9 06 7 8 9 10 11 12 13 10 6 7 8 9 10 11 12
02 10 11 12 13 14 15 16 07 14 15 16 17 18 19 20 11 13 14 15 16 17 18 19
03 17 18 19 20 21 22 23 08 21 22 23 24 25 26 27 12 20 21 22 23 24 25 26
04 24 25 26 27 28 29 30 09 28 29 13 27 28 29 30 31
05 31
April May June
13 1 2 18 1 2 3 4 5 6 7 22 1 2 3 4
14 3 4 5 6 7 8 9 19 8 9 10 11 12 13 14 23 5 6 7 8 9 10 11
15 10 11 12 13 14 15 16 20 15 16 17 18 19 20 21 24 12 13 14 15 16 17 18
16 17 18 19 20 21 22 23 21 22 23 24 25 26 27 28 25 19 20 21 22 23 24 25
17 24 25 26 27 28 29 30 22 29 30 31 26 26 27 28 29 30
July August September
26 1 2 31 1 2 3 4 5 6 35 1 2 3
27 3 4 5 6 7 8 9 32 7 8 9 10 11 12 13 36 4 5 6 7 8 9 10
28 10 11 12 13 14 15 16 33 14 15 16 17 18 19 20 37 11 12 13 14 15 16 17
29 17 18 19 20 21 22 23 34 21 22 23 24 25 26 27 38 18 19 20 21 22 23 24
30 24 25 26 27 28 29 30 35 28 29 30 31 39 25 26 27 28 29 30
31 31
October November December
39 1 44 1 2 3 4 5 48 1 2 3
40 2 3 4 5 6 7 8 45 6 7 8 9 10 11 12 49 4 5 6 7 8 9 10
41 9 10 11 12 13 14 15 46 13 14 15 16 17 18 19 50 11 12 13 14 15 16 17
42 16 17 18 19 20 21 22 47 20 21 22 23 24 25 26 51 18 19 20 21 22 23 24
43 23 24 25 26 27 28 29 48 27 28 29 30 52 25 26 27 28 29 30 31
【问题讨论】:
你读过the documentation吗? @BrenBarn 显然不够彻底。你是说这个板块? “当 groupby() 对象前进时,之前的组不再可见。因此,如果以后需要该数据,则应将其存储为列表” @BrenBarn 那么它本质上一定是这样的:***.com/q/16598244/1025391 - 但是等等 - 这仍然有些不同。我需要考虑一下…… 为什么不将每个组中的值生成为列表,如代码中所述? 不相关:使用x.month
而不是x.strftime('%B')
按月分组和use x.isocalendar()[1]
for a week number。
【参考方案1】:
如文档所述 (c.f.):
当 groupby() 对象被推进时,前一个组不再可见。因此,如果以后需要该数据,则应将其存储为列表
这意味着迭代器被消耗,当代码稍后无序访问返回的迭代器时,即当 groupby 实际迭代时。由于这里进行了分块和交错,迭代发生了乱序。
由于我们迭代的方式,我们观察到这种特定模式(即只有最后一列完全显示)。那就是:
打印第一行的月份名称。因此,直到最后一列月份的迭代器被消耗(并且它们的内容被丢弃)。 groupby() 对象仅在第一列数据之后生成最后一列的月份名称。
我们打印第一周线。因此,第一列已经用尽的迭代器将使用传递给zip_longest()
的默认值自动填充。只有最后一列仍然提供实际数据。
接下来的月份名称行也是如此。
【讨论】:
当您在groupby()
对象上调用next()
(显式或隐式,例如,通过for
-loop)时,迭代器被消耗(如果它们尚未耗尽)。当代码稍后无序访问返回的迭代器时,它们已经耗尽。以上是关于由 itertools.groupby() 生成的迭代器被意外消耗的主要内容,如果未能解决你的问题,请参考以下文章
为啥 itertools.groupby() 不起作用? [复制]
使 Pandas groupby 的行为类似于 itertools groupby