首页 文章

在块中迭代列表的最“pythonic”方法是什么?

提问于
浏览
374

我有一个Python脚本,它将整数列表作为输入,我需要一次使用四个整数 . 不幸的是,我无法控制输入,或者我将它作为四元素元组列表传入 . 目前,我正在以这种方式迭代它:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

它看起来很像“C-think”,这让我怀疑有更多的pythonic方式来处理这种情况 . 迭代后会丢弃该列表,因此无需保留 . 也许这样的事情会更好吗?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

但是,仍然没有“感觉”正确 . : - /

相关问题:How do you split a list into evenly sized chunks in Python?

30 回答

  • 4

    与其他提案类似,但不完全相同,我喜欢这样做,因为它简单易读:

    it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
    for chunk in zip(it, it, it, it):
        print chunk
    
    >>> (1, 2, 3, 4)
    >>> (5, 6, 7, 8)
    

    这样你就不会获得最后的部分块 . 如果你想将 (9, None, None, None) 作为最后一个块,只需使用 itertools 中的 izip_longest .

  • 2

    使用小功能和东西真的不吸引我;我更喜欢使用切片:

    data = [...]
    chunk_size = 10000 # or whatever
    chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
    for chunk in chunks:
        ...
    
  • 1
    def chunker(iterable, n):
        """Yield iterable in chunk sizes.
    
        >>> chunks = chunker('ABCDEF', n=4)
        >>> chunks.next()
        ['A', 'B', 'C', 'D']
        >>> chunks.next()
        ['E', 'F']
        """
        it = iter(iterable)
        while True:
            chunk = []
            for i in range(n):
                try:
                    chunk.append(it.next())
                except StopIteration:
                    yield chunk
                    raise StopIteration
            yield chunk
    
    if __name__ == '__main__':
        import doctest
    
        doctest.testmod()
    
  • 0

    这是一个没有导入支持生成器的chunker:

    def chunks(seq, size):
        it = iter(seq)
        while True:
            ret = tuple(it.next() for _ in range(size))
            if len(ret) == size:
                yield ret
            else:
                raise StopIteration()
    

    使用示例:

    >>> def foo():
    ...     i = 0
    ...     while True:
    ...         i += 1
    ...         yield i
    ...
    >>> c = chunks(foo(), 3)
    >>> c.next()
    (1, 2, 3)
    >>> c.next()
    (4, 5, 6)
    >>> list(chunks('abcdefg', 2))
    [('a', 'b'), ('c', 'd'), ('e', 'f')]
    
  • 91

    如果您不介意使用外部包,可以使用iteration_utilities.grouper来自iteration_utilties 1.它支持所有迭代(不仅仅是序列):

    from iteration_utilities import grouper
    seq = list(range(20))
    for group in grouper(seq, 4):
        print(group)
    

    打印:

    (0, 1, 2, 3)
    (4, 5, 6, 7)
    (8, 9, 10, 11)
    (12, 13, 14, 15)
    (16, 17, 18, 19)
    

    如果长度不是groupsize的倍数,它还支持填充(不完整的最后一组)或截断(丢弃不完整的最后一组)最后一个:

    from iteration_utilities import grouper
    seq = list(range(17))
    for group in grouper(seq, 4):
        print(group)
    # (0, 1, 2, 3)
    # (4, 5, 6, 7)
    # (8, 9, 10, 11)
    # (12, 13, 14, 15)
    # (16,)
    
    for group in grouper(seq, 4, fillvalue=None):
        print(group)
    # (0, 1, 2, 3)
    # (4, 5, 6, 7)
    # (8, 9, 10, 11)
    # (12, 13, 14, 15)
    # (16, None, None, None)
    
    for group in grouper(seq, 4, truncate=True):
        print(group)
    # (0, 1, 2, 3)
    # (4, 5, 6, 7)
    # (8, 9, 10, 11)
    # (12, 13, 14, 15)
    

    1免责声明:我是该套餐的作者 .

  • 1

    您可以使用funcy库中的partitionchunks函数:

    from funcy import partition
    
    for a, b, c, d in partition(4, ints):
        foo += a * b * c * d
    

    这些函数还有迭代器版本 ipartitionichunks ,在这种情况下效率更高 .

    你也可以偷看their implementation .

  • 2

    从Python的itertools docs的recipes部分修改:

    from itertools import izip_longest
    
    def grouper(iterable, n, fillvalue=None):
        args = [iter(iterable)] * n
        return izip_longest(*args, fillvalue=fillvalue)
    

    Example
    在伪代码中保持示例简洁 .

    grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'
    

    Note: izip_longest 是Python 2.6的新功能 . 在Python 3中使用 zip_longest .

  • 0

    另一种方法是使用 iter 的双参数形式:

    from itertools import islice
    
    def group(it, size):
        it = iter(it)
        return iter(lambda: tuple(islice(it, size)), ())
    

    这可以很容易地适应使用填充(这类似于_387664的答案):

    from itertools import islice, chain, repeat
    
    def group_pad(it, size, pad=None):
        it = chain(iter(it), repeat(pad))
        return iter(lambda: tuple(islice(it, size)), (pad,) * size)
    

    这些甚至可以组合用于可选填充:

    _no_pad = object()
    def group(it, size, pad=_no_pad):
        if pad == _no_pad:
            it = iter(it)
            sentinel = ()
        else:
            it = chain(iter(it), repeat(pad))
            sentinel = (pad,) * size
        return iter(lambda: tuple(islice(it, size)), sentinel)
    
  • 3
    def chunker(seq, size):
        return (seq[pos:pos + size] for pos in range(0, len(seq), size))
    # (in python 2 use xrange() instead of range() to avoid allocating a list)
    

    简单 . 简单 . 快速 . 适用于任何序列:

    text = "I am a very, very helpful text"
    
    for group in chunker(text, 7):
       print repr(group),
    # 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'
    
    print '|'.join(chunker(text, 10))
    # I am a ver|y, very he|lpful text
    
    animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']
    
    for group in chunker(animals, 3):
        print group
    # ['cat', 'dog', 'rabbit']
    # ['duck', 'bird', 'cow']
    # ['gnu', 'fish']
    
  • 6

    我喜欢这种方法 . 它感觉简单而不神奇,支持所有可迭代类型,不需要导入 .

    def chunk_iter(iterable, chunk_size):
    it = iter(iterable)
    while True:
        chunk = tuple(next(it) for _ in range(chunk_size))
        if not chunk:
            break
        yield chunk
    
  • 331

    在你的第二种方法中,我会通过这样做前进到下一组4:

    ints = ints[4:]
    

    但是,我没有进行任何性能测量,因此我不知道哪一个可能更有效 .

    话虽如此,我通常会选择第一种方法 . 它并不漂亮,但这通常是与外界联系的结果 .

  • 3

    发布此作为答案,因为我无法发表评论......

    使用map()而不是zip()修复了J.F.Sebastian的答案中的填充问题:

    >>> def chunker(iterable, chunksize):
    ...   return map(None,*[iter(iterable)]*chunksize)
    

    例:

    >>> s = '1234567890'
    >>> chunker(s, 3)
    [('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
    >>> chunker(s, 4)
    [('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
    >>> chunker(s, 5)
    [('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
    
  • 6

    似乎没有一种漂亮的方法可以做到这一点 . Here是一个包含许多方法的页面,包括:

    def split_seq(seq, size):
        newseq = []
        splitsize = 1.0/size*len(seq)
        for i in range(size):
            newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
        return newseq
    
  • 20

    要避免所有转换到列表 import itertools 和:

    >>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
    ...     list(g)
    

    生产环境 :

    ... 
    0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
    2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
    3 [30, 31, 32, 33, 34]
    >>>
    

    我检查了 groupby 并且它没有转换为列表或使用 len 所以我(想)这将延迟每个值的分辨率,直到它实际使用 . 可悲的是,没有一个可用的答案(此时)似乎提供了这种变化 .

    显然,如果你需要依次处理每个项目嵌套一个for循环g:

    for k,g in itertools.groupby(xrange(35), lambda x: x/10):
        for i in g:
           # do what you need to do with individual items
        # now do what you need to do with the whole group
    

    我对此的特别兴趣是需要使用生成器向gmail API提交最多1000个批量的更改:

    messages = a_generator_which_would_not_be_smart_as_a_list
        for idx, batch in groupby(messages, lambda x: x/1000):
            batch_request = BatchHttpRequest()
            for message in batch:
                batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
            http = httplib2.Http()
            self.credentials.authorize(http)
            batch_request.execute(http=http)
    
  • 8

    首先,我设计它将字符串拆分为子字符串以解析包含hex的字符串 .
    今天我把它变成了复杂但仍然很简单的发电机 .

    def chunker(iterable, size, reductor, condition):
        it = iter(iterable)
        def chunk_generator():
            return (next(it) for _ in range(size))
        chunk = reductor(chunk_generator())
        while condition(chunk):
            yield chunk
            chunk = reductor(chunk_generator())
    

    参数:

    明显的

    • iterable 是包含/生成/迭代输入数据的任何可迭代/迭代器/生成器,

    • size 当然是你想要的块大小,

    更有趣

    • reductor 是一个可调用的,它接收生成器迭代块的内容 .
      我'd expect it to return sequence or string, but I don'吨要求 .

    你可以传递这个参数,例如 listtuplesetfrozenset
    或任何发烧友 . 我传递了这个函数,返回字符串
    (假设 iterable 包含/生成/遍历字符串):

    def concatenate(iterable):
        return ''.join(iterable)
    

    请注意,reductor可以通过引发异常来关闭生成器 .

    • condition 是一个可调用的,它接收 reductor 返回的任何内容 .
      它决定批准并收益(通过返回评估为 True 的任何内容),
      或拒绝它并完成发电机的工作(通过返回任何其他或提高异常) .

    iterable 中的元素数量不能被 size 整除时,当 it 耗尽时, reductor 将接收生成器产生的更少元素比 size .
    我们称这些元素为持续元素 .

    我邀请两个函数作为这个参数传递:

    • lambda x:x - 将产生持久元素 .

    • lambda x: len(x)==<size> - 持续元素将被拒绝 .
      使用等于大小的数字替换<size>

  • 256
    from itertools import izip_longest
    
    def chunker(iterable, chunksize, filler):
        return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
    
  • 6

    既然没有人's mentioned it yet here'是一个 zip() 解决方案:

    >>> def chunker(iterable, chunksize):
    ...     return zip(*[iter(iterable)]*chunksize)
    

    仅当序列的长度始终可以被块大小整除时才有效,或者如果不是,则不关心尾随块 .

    例:

    >>> s = '1234567890'
    >>> chunker(s, 3)
    [('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
    >>> chunker(s, 4)
    [('1', '2', '3', '4'), ('5', '6', '7', '8')]
    >>> chunker(s, 5)
    [('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
    

    或者使用itertools.izip返回迭代器而不是列表:

    >>> from itertools import izip
    >>> def chunker(iterable, chunksize):
    ...     return izip(*[iter(iterable)]*chunksize)
    

    可以使用@ΤΖΩΤΖΙΟΥ's answer修复填充:

    >>> from itertools import chain, izip, repeat
    >>> def chunker(iterable, chunksize, fillvalue=None):
    ...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
    ...     args = [it] * chunksize
    ...     return izip(*args)
    
  • 0

    很容易让 itertools.groupby 为您提供可迭代的迭代,而无需创建任何临时列表:

    groupby(iterable, (lambda x,y: (lambda z: x.next()/y))(count(),100))
    

    不要被嵌套的lambdas推迟,外部lambda只运行一次以将 count() 生成器和常量 100 放入内部lambda的范围内 .

    我用它来发送行块到mysql .

    for k,v in groupby(bigdata, (lambda x,y: (lambda z: x.next()/y))(count(),100))):
        cursor.executemany(sql, v)
    
  • 11

    关于 J.F. Sebastian here给出的解决方案:

    def chunker(iterable, chunksize):
        return zip(*[iter(iterable)]*chunksize)
    

    它很聪明,但有一个缺点 - 总是返回元组 . 如何获得字符串?
    当然你可以写 ''.join(chunker(...)) ,但无论如何都会构建临时元组 .

    您可以通过编写自己的 zip 来摆脱临时元组,如下所示:

    class IteratorExhausted(Exception):
        pass
    
    def translate_StopIteration(iterable, to=IteratorExhausted):
        for i in iterable:
            yield i
        raise to # StopIteration would get ignored because this is generator,
                 # but custom exception can leave the generator.
    
    def custom_zip(*iterables, reductor=tuple):
        iterators = tuple(map(translate_StopIteration, iterables))
        while True:
            try:
                yield reductor(next(i) for i in iterators)
            except IteratorExhausted: # when any of iterators get exhausted.
                break
    

    然后

    def chunker(data, size, reductor=tuple):
        return custom_zip(*[iter(data)]*size, reductor=reductor)
    

    用法示例:

    >>> for i in chunker('12345', 2):
    ...     print(repr(i))
    ...
    ('1', '2')
    ('3', '4')
    >>> for i in chunker('12345', 2, ''.join):
    ...     print(repr(i))
    ...
    '12'
    '34'
    
  • 1

    如果列表大小相同,您可以将它们组合成带有 zip() 的4元组列表 . 例如:

    # Four lists of four elements each.
    
    l1 = range(0, 4)
    l2 = range(4, 8)
    l3 = range(8, 12)
    l4 = range(12, 16)
    
    for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
        ...
    

    这是 zip() 函数产生的内容:

    >>> print l1
    [0, 1, 2, 3]
    >>> print l2
    [4, 5, 6, 7]
    >>> print l3
    [8, 9, 10, 11]
    >>> print l4
    [12, 13, 14, 15]
    >>> print zip(l1, l2, l3, l4)
    [(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]
    

    如果列表很大,并且您不想将它们组合成更大的列表,请使用 itertools.izip() ,它会生成迭代器而不是列表 .

    from itertools import izip
    
    for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
        ...
    
  • 0

    我需要一个也适用于集合和生成器的解决方案 . 我无法想出任何非常短而漂亮的东西,但它至少是可读的 .

    def chunker(seq, size):
        res = []
        for el in seq:
            res.append(el)
            if len(res) == size:
                yield res
                res = []
        if res:
            yield res
    

    列表:

    >>> list(chunker([i for i in range(10)], 3))
    [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
    

    组:

    >>> list(chunker(set([i for i in range(10)]), 3))
    [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
    

    发电机:

    >>> list(chunker((i for i in range(10)), 3))
    [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
    
  • 4

    我是粉丝

    chunkSize= 4
    for i in xrange(0, len(ints), chunkSize):
        chunk = ints[i:i+chunkSize]
        # process chunk of size <= chunkSize
    
  • 1

    这个问题的理想解决方案适用于迭代器(不仅仅是序列) . 它也应该很快 .

    这是itertools文档提供的解决方案:

    def grouper(n, iterable, fillvalue=None):
        #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
        args = [iter(iterable)] * n
        return itertools.izip_longest(fillvalue=fillvalue, *args)
    

    在我的mac书空中使用ipython的 %timeit ,每循环得到47.5 us .

    但是,这对我来说真的不起作用,因为结果被填充为偶数组 . 没有填充的解决方案稍微复杂一些 . 最天真的解决方案可能是:

    def grouper(size, iterable):
        i = iter(iterable)
        while True:
            out = []
            try:
                for _ in range(size):
                    out.append(i.next())
            except StopIteration:
                yield out
                break
    
            yield out
    

    简单但很慢:每循环693 us

    我能提出的最佳解决方案是使用 islice 作为内循环:

    def grouper(size, iterable):
        it = iter(iterable)
        while True:
            group = tuple(itertools.islice(it, None, size))
            if not group:
                break
            yield group
    

    使用相同的数据集,我得到每个循环305 us .

    无法以更快的速度获得纯粹的解决方案,我提供了以下解决方案,并提出了一个重要的警告:如果您的输入数据中包含 filldata 的实例,则可能会得到错误的答案 .

    def grouper(n, iterable, fillvalue=None):
        #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
        args = [iter(iterable)] * n
        for i in itertools.izip_longest(fillvalue=fillvalue, *args):
            if tuple(i)[-1] == fillvalue:
                yield tuple(v for v in i if v != fillvalue)
            else:
                yield i
    

    我真的不喜欢这个答案,但速度要快得多 . 每循环124 us

  • 1

    如果列表很大,执行此操作的最高性能方法是使用生成器:

    def get_chunk(iterable, chunk_size):
        result = []
        for item in iterable:
            result.append(item)
            if len(result) == chunk_size:
                yield tuple(result)
                result = []
        if len(result) > 0:
            yield tuple(result)
    
    for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
        print x
    
    (1, 2, 3)
    (4, 5, 6)
    (7, 8, 9)
    (10,)
    
  • 1

    我从不希望我的大块填充,所以这个要求是必不可少的 . 我发现能够处理任何可迭代的能力也是必需的 . 鉴于此,我决定延续接受的答案,https://stackoverflow.com/a/434411/1074659 .

    如果由于需要比较和过滤填充值而不需要填充,则此方法的性能会受到轻微影响 . 但是,对于大块大小,此实用程序非常高效 .

    #!/usr/bin/env python3
    from itertools import zip_longest
    
    
    _UNDEFINED = object()
    
    
    def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
        """
        Collect data into chunks and optionally pad it.
    
        Performance worsens as `chunksize` approaches 1.
    
        Inspired by:
            https://docs.python.org/3/library/itertools.html#itertools-recipes
    
        """
        args = [iter(iterable)] * chunksize
        chunks = zip_longest(*args, fillvalue=fillvalue)
        yield from (
            filter(lambda val: val is not _UNDEFINED, chunk)
            if chunk[-1] is _UNDEFINED
            else chunk
            for chunk in chunks
        ) if fillvalue is _UNDEFINED else chunks
    
  • 1

    单行,即兴解决方案迭代列表 x 大小 4 的块 -

    for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
        ... do something with a, b, c and d ...
    
  • 0

    使用NumPy很简单:

    ints = array([1, 2, 3, 4, 5, 6, 7, 8])
    for int1, int2 in ints.reshape(-1, 2):
        print(int1, int2)
    

    输出:

    1 2
    3 4
    5 6
    7 8
    
  • 1
    def group_by(iterable, size):
        """Group an iterable into lists that don't exceed the size given.
    
        >>> group_by([1,2,3,4,5], 2)
        [[1, 2], [3, 4], [5]]
    
        """
        sublist = []
    
        for index, item in enumerate(iterable):
            if index > 0 and index % size == 0:
                yield sublist
                sublist = []
    
            sublist.append(item)
    
        if sublist:
            yield sublist
    
  • 1
    import itertools
    def chunks(iterable,size):
        it = iter(iterable)
        chunk = tuple(itertools.islice(it,size))
        while chunk:
            yield chunk
            chunk = tuple(itertools.islice(it,size))
    
    # though this will throw ValueError if the length of ints
    # isn't a multiple of four:
    for x1,x2,x3,x4 in chunks(ints,4):
        foo += x1 + x2 + x3 + x4
    
    for chunk in chunks(ints,4):
        foo += sum(chunk)
    

    其他方式:

    import itertools
    def chunks2(iterable,size,filler=None):
        it = itertools.chain(iterable,itertools.repeat(filler,size-1))
        chunk = tuple(itertools.islice(it,size))
        while len(chunk) == size:
            yield chunk
            chunk = tuple(itertools.islice(it,size))
    
    # x2, x3 and x4 could get the value 0 if the length is not
    # a multiple of 4.
    for x1,x2,x3,x4 in chunks2(ints,4,0):
        foo += x1 + x2 + x3 + x4
    
  • 1

    还有一个答案,其优点是:

    1)容易理解
    2)适用于任何可迭代的,而不仅仅是序列(上面的一些答案会阻塞文件句柄)
    3)不会一次将块加载到内存中
    4)不要在内存中创建一个长度相同的迭代器引用列表
    5)列表末尾没有填充填充值

    话虽如此,我还没有计时,所以它可能比一些更聪明的方法更慢,并且考虑到用例,一些优点可能无关紧要 .

    def chunkiter(iterable, size):
      def inneriter(first, iterator, size):
        yield first
        for _ in xrange(size - 1): 
          yield iterator.next()
      it = iter(iterable)
      while True:
        yield inneriter(it.next(), it, size)
    
    In [2]: i = chunkiter('abcdefgh', 3)
    In [3]: for ii in i:                                                
              for c in ii:
                print c,
              print ''
            ...:     
            a b c 
            d e f 
            g h
    

    Update:
    由于内部和外部循环从同一个迭代器中提取值,因此存在一些缺点:
    1)继续没有看起来像是一个问题,因为在外部循环中没有什么可以测试的 .
    2)break在内循环中没有按预期工作 - 控件将在迭代器中的下一个项目中再次在内循环中结束 . 要跳过整个块,要么将内迭代器(上面的ii)包装在一个元组中,例如, for c in tuple(ii) ,或者设置一个标志并耗尽迭代器 .

相关问题