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

我有一个Python脚本,需要input一个整数列表,我需要一次处理四个整数。 不幸的是,我没有控制input,或者我将它作为四元组元素列表传入。 目前,我正在这样迭代它:

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] = [] 

尽pipe如此,仍然不太“感觉”正确。 : – /

相关问题: 如何在Python中将列表分成均匀大小的块?

从Python的itertools文档的recipes部分修改:

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


在伪代码保持例子简洁。

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

注意: izip_longest是Python 2.6的新增function。 在Python 3中使用zip_longest

 def chunker(seq, size): return (seq[pos:pos + size] for pos in xrange(0, len(seq), size)) 

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

 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'] 

我是一个粉丝

 chunkSize= 4 for i in xrange(0, len(ints), chunkSize): chunk = ints[i:i+chunkSize] # process chunk of size <= chunkSize 
 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 
 from itertools import izip_longest def chunker(iterable, chunksize, filler): return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler) 

我需要一个解决scheme,也可以使用集合和生成器。 我不能拿出任何非常短而漂亮的东西,但至less它是可读的。

 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]] 

作为一个答案,因为我不能评论发布…

使用map()而不是zip()修复了JF 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')] 

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

 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

由于没有人提到它,所以这是一个zip()解决scheme:

 >>> 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) 

填充可以使用@ΤΖΩΤΖΙΟΥ的答案来解决 :

 >>> 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) 

使用小function和东西真的不吸引我, 我更喜欢使用切片:

 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: ... 

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

 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,) 

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

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

这可以很容易地适应使用填充(这是类似于马库斯Jarderot的答案):

 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) 

用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 

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

 ints = ints[4:] 

但是,我还没有做过任何绩效评估,所以我不知道哪一个可能更有效率。

话虽如此,我通常会select第一种方法。 这并不漂亮,但这往往是与外界交stream的结果。

这个问题的理想解决scheme适用于迭代器(不只是序列)。 它也应该很快。

这是itertools文档提供的解决scheme:

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

在我的MacBook Air上使用ipython的%timeit ,每个循环得到47.5 us。

然而,这对我来说真的不起作用,因为结果被填充到规模较大的组。 没有填充的解决scheme稍微复杂一些。 最天真的解决scheme可能是:

 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

我能想到的最好的解决scheme是使用内部循环的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。

无法以比这更快的速度获得纯粹的解决scheme,我提供以下解决scheme,并提供一个重要的警告:如果input数据中包含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)容易理解
2)适用于任何迭代,而不仅仅是序列(上面的一些答案会扼杀文件句柄)
3)不一次加载块到内存中
4)不会在内存中创build一个相同迭代器的引用列表
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 '' ...: abcdefgh 

更新:
由于内部和外部循环都从相同的迭代器中提取数值,所以有一些缺点:
1)在外部循环中继续不能按预期工作 – 它只是继续到下一个项目,而不是跳过一个块。 然而,这看起来不是一个问题,因为在外部循环中没有什么可以testing的。
2)break在内部循环中无法正常工作 – 控制将在迭代器中的下一个项目中再次在内部循环中结束。 为了跳过整个块,可以将内部迭代器(ii)置于一个元组中,例如for c in tuple(ii) ,或者设置一个标志并且耗尽迭代器。

 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 

您可以使用funcy库中的分区或块函数:

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

这些函数也有迭代器版本的ipartitionichunks ,在这种情况下会更有效率。

你也可以偷看他们的实现 。

要避免所有转换为列表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所以我(想)这将延迟每个值的parsing,直到它被实际使用。 可悲的是现在没有任何可用的答案似乎提供了这种变化。

很明显,如果你需要处理每个项目,依次嵌套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 

我特别感兴趣的是需要消费一个生成器来将批量最多1000个变化提交给gmail API:

  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) 

关于JF Sebastian 在这里提供的解决scheme:

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

这很聪明,但有一个缺点 – 总是返回元组。 如何获得string呢?
当然,你可以写''.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' 

这是一个没有支持生成器的import的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')] 
 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() 

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

 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 

似乎没有一个漂亮的方法来做到这一点。 这是一个有多种方法的页面,其中包括:

 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 

如果列表大小相同,则可以将它们与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): ... 

单线程,adhoc解决scheme遍历大小为4块的列表x

 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 ... 

起初,我devise它将string拆分成子string来parsing包含hex的string。
今天我把它变成了复杂的,但仍然是简单的发电机。

 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是任何迭代/迭代器/发生器包含/产生/迭代input数据,
  • size当然是你想要的块的大小,

更有意思的

  • reductor是可调用的,它接收发生器遍历chunk的内容。
    我希望它会返回序列或string,但我不要求。

    你可以通过这个参数来传递例如listtuplesetfrozenset
    或更奇特的。 我会通过这个函数,返回string
    (前提是iterable包含/生成/迭代string):

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

    请注意, reductor可以通过引发exception来引起闭合发生器。

  • condition是什么reductor返回接收任何东西的callable。
    它决定批准和产生(通过返回任何评估为True ),
    或拒绝它并完成发电机的工作(通过返回其他任何东西或引发exception)。

    iterable的元素的数量不能被size整除时,当it被耗尽时, reductor将接收到发生器产生的元素less于size
    我们称这些元素为元素

    我邀请了两个函数作为这个parameter passing:

    • lambda x:x最后的元素将被放弃。

    • lambda x: len(x)==<size>最后的元素将被拒绝。
      用等于size数字replace<size>

使itertools.groupby可以轻松地获得可迭代的迭代,而不会创build任何临时列表:

 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) 

相当pythonic在这里(你也可以内联split_groups函数的主体)

 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 

这个答案分裂了一个string列表 ,f.ex. 实现PEP8线路长度符合性:

 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 

用于

 >>> 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']]