由 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 的反面?

为啥 itertools.groupby() 不起作用? [复制]

python中的itertools.groupby()

使 Pandas groupby 的行为类似于 itertools groupby

itertools.groupby 返回错误的结果(这与排序无关)[重复]

为啥 itertools.groupby 可以将 NaN 分组在列表中而不是 numpy 数组中