问题描述
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,key=itemgetter('k'))}
for k,v in r.items():
print(k,list(v)) # v1 []
似乎有些怪癖,还是我遗漏了什么?
解决方法
这是itertools.groupby
的{{3}}:
返回的组本身就是一个迭代器,它与 groupby() 共享底层的可迭代对象。因为源是共享的,当 groupby() 对象前进时,之前的组不再可见。因此,如果稍后需要该数据,则应将其存储为列表
换句话说,在获取迭代器中的下一项之前,您需要访问该组——在这种情况下,是从 dict comprehension 内部获取。要在 dict 理解中使用它,您需要在理解中列出:
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'}]
在您的第一个示例中,因为您使用的是生成器表达式,所以您实际上不会开始迭代 groupby 迭代器,直到您开始 for
循环。但是,如果您使用非惰性列表推导式而不是生成器(即 r = [(k,v) for k,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()
时都会打印一行,然后再生成值以帮助了解发生了什么。当我们在 next()
上调用 grouper
时,这会导致下一次调用 g
并获取第一个组和值:
> k,gr = next(grouper)
yielding: 1
现在对 next()
的每个 gr
调用都会导致对底层 next()
的 g
调用,正如您从打印中看到的:
> 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
现在看看如果我们在 next()
上调用 grouper
来获取下一组会发生什么:
> next(grouper)
yielding: 3
yielding: 4
yielding: 5
yielding: 6
yielding: 7
yielding: 8
yielding: 9
yielding: -1
Groupby 通过生成器进行迭代,直到它遇到改变键的值。值已由 g
产生。我们不能再获取 gr
的下一个值(即 3),除非我们以某种方式存储了所有这些值,或者我们以某种方式将底层 g
生成器转换为两个独立的生成器。对于默认实现,这些都不是很好的解决方案(特别是因为 itertools 的重点是不这样做),所以它让你来做这件事,但你需要存储这些值before 某事导致 next(grouper)
被调用并使生成器超过您想要的值。