字典理解丢弃元素

dict comprehension discards elements

from itertools import groupby
from operator import itemgetter

d = [{'k': 'v1'}]

r = ((k, v) for k, v in groupby(d, key=itemgetter('k')))
for k, v in r:
    print(k, list(v)) # v1 [{'k': 'v1'}]

print('---')
r = {k: v for k, v in groupby(d, key=itemgetter('k'))}
for k, v in r.items():
    print(k, list(v)) # v1 []

似乎有些怪癖,还是我遗漏了什么?

这是 itertools.groupbydocumented part

The returned group is itself an iterator that shares the underlying iterable with groupby(). Because the source is shared, when the groupby() object is advanced, the previous group is no longer visible. So, if that data is needed later, it should be stored as a list

换句话说,您需要在获取迭代器中的下一项之前访问该组——在本例中,是从字典理解中获取。要在字典理解中使用它,您需要在理解中列出列表:

from itertools import groupby
from operator import itemgetter

d = [{'k': 'v1'}]

r = {k: list(v) for k, v in groupby(d, key=itemgetter('k'))}
for k, v in r.items():
    print(k, v) # v1 [{'k': 'v1'}]

在您的第一个示例中,因为您使用的是生成器表达式,所以在启动 for 循环之前,您实际上并没有开始迭代 groupby 迭代器。但是,如果您使用非惰性列表理解而不是生成器(即 r = [(k, v) for k, v in groupby(d, key=itemgetter('k'))]),您会遇到同样的问题。

为什么会这样?

保留惰性迭代是 itertools 背后的激励思想。因为它处理的是(可能很大或无限的)迭代器,所以它从不想在内存中存储任何值。它只是在底层迭代器上调用 next() 并用那个值做一些事情。调用 next() 后,您将无法返回到之前的值(不存储它们,itertools 不想这样做)。

使用 groupby 可以通过示例更容易理解。这是一个简单的生成器,它生成交替范围的正数和负数,以及一个将它们分组的 groupby 迭代器:

def make_groups():
    i = 1
    while True:
        for n in range(1, 10):
            print("yielding: ", n*i)
            yield n * i
        i *= -1

g = make_groups()

grouper = groupby(g, key=lambda x: x>0)

make_groups 每次调用 next() 时都会打印一行,然后生成值以帮助了解发生了什么。当我们在 grouper 上调用 next() 时,这会导致下一次调用 g 并得到第一组和值:

> k, gr = next(grouper)
yielding:  1

现在,对 gr 的每个 next() 调用都会导致对基础 gnext() 调用,正如您从打印中看到的那样:

> next(gr)
1              # already have this value from the initial next(grouper)
> next(gr)
yielding:  2   # gets the next value and clicks the underlying generator to the next yield
2 

现在看看如果我们在 grouper 上调用 next() 得到下一组会发生什么:

> next(grouper)

yielding:  3
yielding:  4
yielding:  5
yielding:  6
yielding:  7
yielding:  8
yielding:  9
yielding:  -1

Groupby 在生成器中迭代,直到它找到一个改变键的值。这些值已由 g 产生。我们不能再获取 gr 的下一个值(即 3),除非我们以某种方式存储了所有这些值,或者我们以某种方式将底层 g 生成器转换为两个独立的生成器。这些都不是默认实现的好解决方案(特别是因为 itertools 的目的是 not 来做到这一点),所以它留给你来做,但你需要存储这些值 before 某些东西导致 next(grouper) 被调用并使生成器超过你想要的值。