【问题标题】:Python groupby behaves strangely [duplicate]Python groupby 行为异常[重复]
【发布时间】:2014-10-23 17:17:48
【问题描述】:
from itertools import groupby

source = [ [1,2], [1,3], [2, 1] ]
gby = groupby(source, lambda x: x[0])

print 'as list'
for key, vals in list(gby):
    print 'key {}'.format(key)
    for val in vals:
        print '  val {}'.format(val)

print

print 'as iter'
gby = groupby(source, lambda x: x[0])
for key, vals in gby:
    print 'key {}'.format(key)
    for val in vals:
        print '  val {}'.format(val)

结果:

as list
key 1
key 2
  val [2, 1]

as iter
key 1
  val [1, 2]
  val [1, 3]
key 2
  val [2, 1]

list(gby) 有什么问题?我希望list 是纯函数,它是如何破坏内部状态的?

【问题讨论】:

    标签: python itertools


    【解决方案1】:

    documentation 对此做了说明:

    返回的组本身就是一个迭代器,它共享底层 可使用 groupby() 进行迭代。因为源是共享的,当 groupby() 对象是高级的,以前的组不再可见。 因此,如果以后需要该数据,则应将其存储为列表:

    groups = []
    uniquekeys = []
    data = sorted(data, key=keyfunc)
    for k, g in groupby(data, keyfunc):
        groups.append(list(g))      # Store group iterator as a list
        uniquekeys.append(k)
    

    在尝试遍历返回的组迭代器之前,您已经耗尽了 groupby 对象(通过将其转换为列表),因此除了最后一个组之外的所有组都将丢失。

    通过查看函数的 Python 实现更容易找出原因:

    class groupby(object):
        # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
        # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
        def __init__(self, iterable, key=None):
            if key is None:
                key = lambda x: x
            self.keyfunc = key
            self.it = iter(iterable)
            self.tgtkey = self.currkey = self.currvalue = object()
        def __iter__(self):
            return self
        def next(self):
            while self.currkey == self.tgtkey:
                self.currvalue = next(self.it)
                self.currkey = self.keyfunc(self.currvalue)
            self.tgtkey = self.currkey
            return (self.currkey, self._grouper(self.tgtkey))
        def _grouper(self, tgtkey):  # This is the "group" iterator
            while self.currkey == tgtkey:  # self.currkey != tgtkey if you advance groupby and then try to use this object.
                yield self.currvalue
                self.currvalue = next(self.it)
                self.currkey = self.keyfunc(self.currvalue)
    

    调用next(groupby) 将指向底层可迭代对象(self.currvalue) 的内部指针前进到下一个键,然后返回当前键(self.currkey) 和_grouper 迭代器。 _grouper 将当前键作为参数(称为tgtkey),并将产生值(并重新计算self.currkey),直到self.currkeytgtkey 不同,这意味着它返回了与当前密钥。因此,如果您在使用 _grouper 对象之前提前 groupbyself.currkey永远等于 tgtkey,因此 _grouper 迭代器将不会返回任何内容。

    如果出于某种原因您确实需要将groupby 结果存储在列表中,您必须这样做:

    gby_list = []
    for key, vals in gby:
        gby_list.append(key, list(vals))
    

    或者:

    gby_list = [key, list(vals) for key, vals in gby]
    

    【讨论】:

    • 你能解释一下为什么vals 没有第一次打印吗?我的意思是在“as list”代码 sn-p 的第一次迭代中。
    • OP 的代码将如何包含此内容?
    • groupby 迭代器产生两个东西:keygroup 迭代器。 group 迭代器与 groupby 迭代器共享底层迭代器。这意味着当您推进 groupby 迭代器时,group 迭代器现在无用(请参阅我的编辑以了解原因)。因此,在您的第一个循环中,您调用的是list(gby)。这将遍历整个groupby 对象,并将内容存储在列表中。这意味着返回的所有 group 迭代器,除了最后一个之外,都是无用的。
    • IOW,总是groupby视为一个迭代器,并在groupby产生grouper对象时立即使用它们。
    • @PadraicCunningham 我添加了示例代码来展示如何将 groupby 迭代器存储为列表,但在大多数情况下,您可能只想将其实际用作迭代器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-17
    • 2018-03-09
    • 1970-01-01
    相关资源
    最近更新 更多