What is the most “pythonic” way to iterate over a list in chunks?
我有一个python脚本,它将一个整数列表作为输入,我需要一次处理四个整数。不幸的是,我没有对输入的控制权,或者将它作为四元素元组的列表传入。目前,我以这种方式迭代它:
1 2 3 | 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",这让我怀疑有一种更为Python式的方式来处理这种情况。列表在迭代后被丢弃,因此不需要保留它。也许这样更好?
1 2 3 | while ints: foo += ints[0] * ints[1] + ints[2] * ints[3] ints[0:4] = [] |
不过,还是感觉不太对。- -
相关问题:如何在Python中将列表分割成大小均匀的块?
1 2 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) |
简单。容易的。快。使用任何序列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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'] |
从python的itertools文档的"配方"部分修改:
1 2 3 4 5 | from itertools import zip_longest def grouper(iterable, n, fillvalue=None): args = [iter(iterable)] * n return zip_longest(*args, fillvalue=fillvalue) |
例子在伪代码中保持示例的简洁。
1 | grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx' |
注意:在python 2上,使用
我是个迷
1 2 3 4 | chunk_size= 4 for i in range(0, len(ints), chunk_size): chunk = ints[i:i+chunk_size] # process chunk of size <= chunk_size |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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) |
另一种方式:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 2 3 4 | from itertools import izip_longest def chunker(iterable, chunksize, filler): return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler) |
我需要一个解决方案,也可以与发电机组和发电机一起工作。我想不出任何非常短和漂亮的东西,但至少它是可读的。
1 2 3 4 5 6 7 8 9 | def chunker(seq, size): res = [] for el in seq: res.append(el) if len(res) == size: yield res res = [] if res: yield res |
名单:
1 2 | >>> list(chunker([i for i in range(10)], 3)) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] |
设置:
1 2 | >>> list(chunker(set([i for i in range(10)]), 3)) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] |
发电机:
1 2 | >>> list(chunker((i for i in range(10)), 3)) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] |
这个问题的理想解决方案是使用迭代器(而不仅仅是序列)。它也应该很快。
这是ITertools文档提供的解决方案:
1 2 3 4 | def grouper(n, iterable, fillvalue=None): #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n return itertools.izip_longest(fillvalue=fillvalue, *args) |
使用ipython的
然而,这对我真的不起作用,因为结果被填充成大小相等的组。没有填充的解决方案稍微复杂一些。最天真的解决方案可能是:
1 2 3 4 5 6 7 8 9 10 11 12 | 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
我能想到的最好的解决方案是使用
1 2 3 4 5 6 7 | def grouper(size, iterable): it = iter(iterable) while True: group = tuple(itertools.islice(it, None, size)) if not group: break yield group |
使用相同的数据集,每个循环获得305 us。
我无法以更快的速度获得纯解决方案,我提供了以下解决方案,但需要注意的是:如果您的输入数据中有
1 2 3 4 5 6 7 8 | 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美
与其他建议类似,但不完全相同,我喜欢这样做,因为它简单易读:
1 2 3 4 5 6 | 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) |
这样就不会得到最后的部分块。如果你想把
由于还没有人提到它,这里有一个
1 2 | >>> def chunker(iterable, chunksize): ... return zip(*[iter(iterable)]*chunksize) |
只有当序列的长度总是可以被块大小整除,或者不关心尾随的块(如果不是尾随的块)时,它才起作用。
例子:
1 2 3 4 5 6 7 | >>> 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返回迭代器而不是列表:
1 2 3 | >>> from itertools import izip >>> def chunker(iterable, chunksize): ... return izip(*[iter(iterable)]*chunksize) |
可以使用@932;_Ω__磴_的答案来固定衬垫:
1 2 3 4 5 | >>> 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) |
使用map()而不是zip()修复了j.f.Sebastian答案中的填充问题:
1 2 | >>> def chunker(iterable, chunksize): ... return map(None,*[iter(iterable)]*chunksize) |
例子:
1 2 3 4 5 6 7 | >>> 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')] |
如果您不介意使用外部包,您可以使用来自
1 2 3 4 | from iteration_utilities import grouper seq = list(range(20)) for group in grouper(seq, 4): print(group) |
哪些印刷品:
1 2 3 4 5 | (0, 1, 2, 3) (4, 5, 6, 7) (8, 9, 10, 11) (12, 13, 14, 15) (16, 17, 18, 19) |
如果长度不是GroupSize的倍数,它还支持填充(不完整的最后一个组)或截断(丢弃不完整的最后一个组)最后一个组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 2 3 4 5 | 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 2 3 4 5 | from itertools import islice def group(it, size): it = iter(it) return iter(lambda: tuple(islice(it, size)), ()) |
这可以很容易地适应使用填充(这类似于Markus Jarderot的回答):
1 2 3 4 5 | 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) |
这些甚至可以组合用于可选填充:
1 2 3 4 5 6 7 8 9 | _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) |
有了numpy,很简单:
1 2 3 | 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 | 1 2 3 4 5 6 7 8 |
下面是一个不带导入的chunker,它支持生成器:
1 2 3 4 5 6 7 8 | def chunks(seq, size): it = iter(seq) while True: ret = tuple(next(it) for _ in range(size)) if len(ret) == size: yield ret else: raise StopIteration() |
使用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> 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')] |
在您的第二种方法中,我将通过这样做前进到下一组4:
1 | ints = ints[4:] |
但是,我没有做任何性能度量,所以我不知道哪一个更有效。
既然如此,我通常会选择第一种方法。这并不漂亮,但这往往是与外界交往的结果。
我从不希望我的大块被填充,所以这一要求是必要的。我发现在任何不可测的地方工作的能力也是必要的。考虑到这一点,我决定扩展接受的答案:https://stackoverflow.com/a/434411/1074659。
如果由于需要比较和过滤填充值而不需要填充,那么在这种方法中性能会受到轻微影响。但是,对于大的块大小,这个实用程序的性能非常好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #!/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 2 3 4 5 6 7 | 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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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(next(it)) except StopIteration: yield chunk raise StopIteration yield chunk if __name__ == '__main__': import doctest doctest.testmod() |
关于
1 2 | def chunker(iterable, chunksize): return zip(*[iter(iterable)]*chunksize) |
它很聪明,但有一个缺点——总是返回tuple。如何获取字符串?当然,您可以编写
您可以通过编写自己的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
然后
1 2 | def chunker(data, size, reductor=tuple): return custom_zip(*[iter(data)]*size, reductor=reductor) |
示例用法:
1 2 3 4 5 6 7 8 9 10 | >>> 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)容易理解2)适用于任何不可重复的,而不仅仅是序列(上面的一些答案会扼杀文件句柄)3)不同时将块加载到内存中4)不会在内存中列出对同一迭代器的长块引用列表5)列表末尾没有填充填充值
尽管如此,我还没有对它进行计时,因此它可能比一些更聪明的方法要慢,并且考虑到用例,一些优势可能是不相关的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
更新:由于内部循环和外部循环从同一迭代器中提取值这一事实,有两个缺点:1)Continue在外循环中不能按预期工作-它只继续下一个项目,而不是跳过一个块。但是,这似乎不是一个问题,因为在外部循环中没有任何要测试的内容。2)break在内部循环中不能按预期工作-控制将在内部循环中再次结束,迭代器中的下一个项。要跳过整个块,可以将内部迭代器(ii)包装在一个元组中,例如
为了避免所有转换为清单
1 2 | >>> for k, g in itertools.groupby(xrange(35), lambda x: x/10): ... list(g) |
生产:
1 2 3 4 5 6 | ... 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] >>> |
我检查了
显然,如果您需要依次处理每个项,那么在g上嵌套一个for循环:
1 2 3 4 | 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 |
我对此特别感兴趣的是需要使用一个生成器,以将高达1000个批次的更改提交给Gmail API:
1 2 3 4 5 6 7 8 | 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) |
您可以使用Funcy库中的分区或块函数:
1 2 3 4 | from funcy import partition for a, b, c, d in partition(4, ints): foo += a * b * c * d |
这些函数还有迭代器版本
您还可以查看它们的实现。
一个线性,即席的解决方案,在一个列表中迭代一块大小为
1 2 | 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 ... |
起初,我设计它将字符串拆分成子字符串来解析包含十六进制的字符串。今天我把它变成了一个复杂的,但仍然很简单的发电机。
1 2 3 4 5 6 7 8 | 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 是包含/生成/迭代输入数据的任何iterable/iterator/generator,- 当然,
size 是你想要得到的块的大小,
更有趣
reductor 是可调用的,它接收生成器对块内容的迭代。我希望它返回序列或字符串,但我不要求这样做。你可以把这个论点作为例子,比如
list 、tuple 、set 、frozenset ,或者其他更漂亮的东西。我将传递这个函数,返回字符串(前提是iterable 包含/生成/迭代字符串):1
2def concatenate(iterable):
return ''.join(iterable)注意,
reductor 通过引发异常可能导致关闭发电机。condition 是可调用的,它接收reductor 返回的任何内容。它决定批准并放弃它(通过将任何评估结果返回给True )或者拒绝并完成发电机的工作(返回任何其他内容或引发异常)。当
iterable 中的元素数不可被size 除尽时,it 耗尽时,reductor 将接收产生比size 少元素的发电机。让我们称这些元素为持久元素。我邀请了两个函数作为参数传递:
lambda x:x —将生成最后的元素。lambda x: len(x)== —最后的元素将被拒绝。用等于size 的数字替换 。
似乎没有一个很好的方法来做到这一点。这是一个有很多方法的页面,包括:
1 2 3 4 5 6 | 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 |
如果列表的大小相同,可以将它们与
1 2 3 4 5 6 7 8 9 | # 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): ... |
下面是
1 2 3 4 5 6 7 8 9 10 | >>> 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)] |
如果列表很大,并且您不想将它们组合成一个更大的列表,那么使用生成迭代器的
1 2 3 4 | from itertools import izip for i1, i2, i3, i4 in izip(l1, l2, l3, l4): ... |
这里有相当多的Python(你也可以把
1 2 3 4 5 6 | import itertools def split_groups(iter_in, group_size): return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size)) for x, y, z, w in split_groups(range(16), 4): foo += x * y + z * w |
此答案拆分字符串列表,例如,为了实现PEP8行长度一致性:
1 2 3 4 5 6 7 8 9 10 11 12 | def split(what, target_length=79): '''splits list of strings into sublists, each having string length at most 79''' out = [[]] while what: if len("', '".join(out[-1])) + len(what[0]) < target_length: out[-1].append(what.pop(0)) else: if not out[-1]: # string longer than target_length out[-1] = [what.pop(0)] out.append([]) return out |
用作
1 2 | >>> split(['deferred_income', 'long_term_incentive', 'restricted_stock_deferred', 'shared_receipt_with_poi', 'loan_advances', 'from_messages', 'other', 'director_fees', 'bonus', 'total_stock_value', 'from_poi_to_this_person', 'from_this_person_to_poi', 'restricted_stock', 'salary', 'total_payments', 'exercised_stock_options'], 75) [['deferred_income', 'long_term_incentive', 'restricted_stock_deferred'], ['shared_receipt_with_poi', 'loan_advances', 'from_messages', 'other'], ['director_fees', 'bonus', 'total_stock_value', 'from_poi_to_this_person'], ['from_this_person_to_poi', 'restricted_stock', 'salary', 'total_payments'], ['exercised_stock_options']] |
除非我遗漏了一些东西,否则下面关于生成器表达式的简单解决方案没有被提到。它假定块的大小和数量都是已知的(通常是这样),并且不需要填充:
1 2 3 4 5 6 | def chunks(it, n, m): """Make an iterator over m first chunks of size n. """ it = iter(it) # Chunks are presented as tuples. return (tuple(next(it) for _ in range(n)) for _ in range(m)) |
我希望通过将迭代器从列表中转出,我不只是复制列表的一部分。生成器可以被切片,并且它们仍然是一个生成器,而列表将被切片为1000个条目的大块,这会降低效率。
1 2 3 4 5 6 7 8 9 10 11 | def iter_group(iterable, batch_size:int): length = len(iterable) start = batch_size*-1 end = 0 while(end < length): start += batch_size end += batch_size if type(iterable) == list: yield (iterable[i] for i in range(start,min(length-1,end))) else: yield iterable[start:end] |
用途:
1 2 3 4 5 | items = list(range(1,1251)) for item_group in iter_group(items, 1000): for item in item_group: print(item) |
在不创建任何临时列表的情况下,很容易使
1 | groupby(iterable, (lambda x,y: (lambda z: x.next()/y))(count(),100)) |
不要被嵌套的lambda延迟,外部lambda只运行一次,将
我使用它向MySQL发送大量的行。
1 2 | for k,v in groupby(bigdata, (lambda x,y: (lambda z: x.next()/y))(count(),100))): cursor.executemany(sql, v) |