字典理解丢弃元素 为什么会这样?

问题描述

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) 被调用并使生成器超过您想要的值。