我试图理解我在今天早些时候写过的_1658709中发现的一个怪癖 . 基本上,我正在从包含 itertools.groupby
的生成器函数中产生组 . 我发现有趣的是,如果我在赋值的左侧解包生成器,则生成器的最后一个元素仍然存在 . 例如:
# test_gb.py
from itertools import groupby
from operator import itemgetter
inputs = ((x > 5, x) for x in range(10))
def make_groups(inputs):
for _, group in groupby(inputs, key=itemgetter(0)):
yield group
a, b = make_groups(inputs)
print(list(a))
print(list(b))
在Cpython上,这导致:
$ python3 ~/sandbox/test_gb.py
[]
[(True, 9)]
这是CPython2.7和CPython3.5的情况 .
在PyPy上,它导致:
$ pypy ~/sandbox/test_gb.py
[]
[]
在这两种情况下,第一个空列表(“ a
”)很容易解释 - 只要需要下一个元素,就会消耗 itertools
中的组 . 因为我们没有失去以太 .
在我看来, PyPy
版本对于第二个空列表(“ b
”)也是有意义的...当解压缩时,我们也消耗 b
(因为python需要查找's after to make sure that it shouldn'为错误的项目数量抛出 ValueError
打开包装) . 出于某种原因, CPython
版本保留了输入可迭代的最后一个元素......任何人都可以解释为什么会这样?
Edit
这可能或多或少显而易见,但我们也可以将它写成:
inputs = ((x > 5, x) for x in range(10))
(_, a), (_, b) = groupby(inputs, key=itemgetter(0))
print(list(a))
print(list(b))
并得到相同的结果......
1 回答
这是因为
groupby
对象处理簿记,grouper
对象只引用它们的key
和父groupby
对象:因为你在解压缩
groupby
对象时没有迭代grouper
对象,所以'll ignore them for now. So what'有趣的是groupby
当你在它上面调用_1658734时会发生什么:我删除了所有不相关的异常处理代码并删除或简化了纯引用计数内容 . 这里有趣的是,当你到达迭代器的末尾
gbo->currkey
,gbo->currvalue
和gbo->tgtkey
未设置为NULL
时,它们仍将指向最后遇到的值(迭代器的最后一项),因为它只是return NULL
PyIter_Next(gbo->it) == NULL
.完成后,你有两个
grouper
对象 . 第一个将tgtvalue
False
,第二个True
. 让我们来看看当你在这些grouper
上调用next
时会发生什么:所以请记住
currvalue
是 notNULL
,所以第一个if
分支并不有意思 . 对于你的第一个石斑鱼,它会比较grouper
和groupby
对象的tgtkey
,看到它们不同,它会立即return NULL
. 所以你有一个空列表 .对于第二个迭代器,
tgtkey
是相同的,因此它将 return the currvalue of the groupby object (这是迭代器中最后遇到的值!),但这次它将groupby
对象的currvalue
和currkey
设置为NULL
.切换回python:如果
grouper
与groupby
中的最后一个组具有相同的tgtkey
,则会发生真正有趣的怪癖:g1
中的一个元素根本不属于第一组 - 但是因为第一个石斑鱼对象的tgtkey
是False
而最后一个tgtkey
是False
,第一个石斑鱼认为它属于第一组 . 它还使groupby
对象无效,因此第三组现在为空 .所有代码都取自the Python source code但缩短了 .