Jaki jest najbardziej "pythoniczny" sposób iteracji listy w kawałkach?

Mam skrypt Pythona, który przyjmuje jako wejście listę liczb całkowitych, które muszę pracować z czterema liczbami całkowitymi na raz. Niestety, nie mam kontroli nad wejściem, albo chciałbym go przekazać jako listę czteroelementowych krotek. Obecnie iterację nad tym robię w ten sposób:

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]
Wygląda to jak "C-think", co sprawia, że podejrzewam, że istnieje bardziej pythoniczny sposób radzenia sobie z tą sytuacją. Lista jest odrzucana po iteracji, więc nie musi być zachowany. Może coś takiego byłoby lepsze?
while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []
Nadal nie czuję się dobrze. :-/

Podobne pytanie: Jak podzielić listę na równe kawałki w Pythonie?

Author: Community, 2009-01-12

30 answers

Modified from the recipes section of Python ' s itertools docs:

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Przykład
W pseudokodzie, aby przykład był zwięzły.

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

Uwaga: w Pythonie 2 Użyj izip_longest zamiast zip_longest.

 365
Author: Craz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-25 19:36:26
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)

Działa z dowolną sekwencją:

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']
 457
Author: nosklo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-31 15:07:16

Jestem fanem

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size
 161
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-26 18:27:49
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)

Inny sposób:

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
 25
Author: Markus Jarderot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-12 03:14:04

Potrzebowałem rozwiązania, które będzie również działać z zestawami i generatorami. Nie mogłem wymyślić niczego bardzo krótkiego i ładnego, ale przynajmniej jest całkiem czytelny.

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

Lista:

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

Zestaw:

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

Generator:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
 14
Author: bcoughlan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-14 23:24:51
from itertools import izip_longest

def chunker(iterable, chunksize, filler):
    return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
 12
Author: Pedro Henriques,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-12 14:33:36

Jeśli nie masz nic przeciwko użyciu zewnętrznego pakietu, możesz użyć iteration_utilities.grouper od iteration_utilties 1. Obsługuje wszystkie iterable (nie tylko sekwencje):

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

Który drukuje:

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

W przypadku, gdy długość nie jest wielokrotnością rozmiaru grupy, obsługuje również wypełnienie (niekompletna ostatnia grupa) lub obcięcie (odrzucenie niekompletnej ostatniej grupy) ostatniej:

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)

Benchmarki

Postanowiłem również porównać czas działania kilku z wymienione podejścia. Jest to Wykres log-log grupujący w grupy "10" elementów na podstawie listy o różnej wielkości. Dla wyników jakościowych: niższy oznacza szybszy:

Tutaj wpisz opis obrazka

Przynajmniej w tym benchmarku iteration_utilities.grouper wypada najlepiej. Następnie podejście Craz .

Benchmark został stworzony z simple_benchmark1. Kod użyty do uruchomienia tego benchmarka to:

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

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

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(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))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 Zastrzeżenie: jestem autorem biblioteki iteration_utilities i simple_benchmark.

 12
Author: MSeifert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-17 20:48:29

Idealne rozwiązanie tego problemu działa z iteratorami (nie tylko sekwencjami). Powinno być również szybkie.

Jest to rozwiązanie dostarczone przez dokumentację dla 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)

Używając ipythona %timeit na moim Mac book air, dostaję 47.5 us na pętlę.

Jednak to naprawdę nie działa dla mnie, ponieważ wyniki są wyściełane być nawet wielkości grup. Rozwiązanie bez wyściółki jest nieco bardziej skomplikowane. Najbardziej naiwnym rozwiązaniem może być:

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

Proste, ale dość wolno: 693 us na pętlę

Najlepsze rozwiązanie jakie mogłem wymyślić islice dla pętli wewnętrznej:

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

Z tym samym zestawem danych, dostaję 305 us na pętlę.

Nie mogąc szybciej uzyskać czystego rozwiązania, podaję następujące rozwiązanie z ważnym zastrzeżeniem: jeśli Twoje dane wejściowe zawierają instancje filldata, możesz uzyskać złą odpowiedź.

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
Nie podoba mi się ta odpowiedź, ale jest znacznie szybsza. 124 us na pętlę
 11
Author: rhettg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-29 00:50:20

Podobne do innych propozycji, ale nie do końca identyczne, Lubię to robić w ten sposób, ponieważ jest proste i łatwe do odczytania:

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)
W ten sposób nie dostaniesz ostatniego kawałka. Jeśli chcesz uzyskać (9, None, None, None) jako ostatni kawałek, po prostu użyj izip_longest z itertools.
 8
Author: kriss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-06 01:56:30

Ponieważ nikt jeszcze o tym nie wspomniał, oto zip() rozwiązanie:

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

Działa tylko wtedy, gdy długość Twojej sekwencji jest zawsze podzielna przez rozmiar fragmentu lub nie zależy ci na końcowym fragmencie, jeśli tak nie jest.

Przykład:

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

Lub używając itertools.izip aby zwrócić iterator zamiast listy:

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

Padding można naprawić za pomocą @ ΤΖΩΤΖΙΟΥ ' 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)
 7
Author: jfs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:14

W Pythonie 3.8 możesz użyć operatora Morsa i itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 10)                                                         
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

 7
Author: kafran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-26 04:14:34

Pakiet more-itertools ma metodę chunked , która robi dokładnie to:

import more_itertools
for s in more_itertools.chunked(range(9), 4):
    print(s)

Druki

[0, 1, 2, 3]
[4, 5, 6, 7]
[8]

chunked zwraca elementy z listy. Jeśli wolisz iteraby, użyj ichunked .

 7
Author: teekarna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-11 04:15:40

Innym podejściem byłoby użycie dwuargumentowej formy iter:

from itertools import islice

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

To może być łatwo dostosowane do użycia padding (jest to podobne do Markus Jarderot 's odpowiedź):

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)

Można je nawet łączyć dla opcjonalnej wyściółki:

_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)
 5
Author: senderle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:37

Użycie map () zamiast zip () rozwiązuje problem z wypełnieniem w odpowiedzi J. F. Sebastiana:

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

Przykład:

>>> 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')]
 5
Author: catwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-31 22:31:47

Jeśli lista jest duża, najlepszym sposobem na to będzie użycie generatora:

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,)
 4
Author: Robert Rossney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-12 04:17:03

Używanie małych funkcji i rzeczy naprawdę do mnie nie przemawia; wolę po prostu używać plasterków:

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:
    ...
 3
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-21 10:40:10

Aby uniknąć wszystkich konwersji do listy import itertools i:

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

Produkuje:

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

Sprawdziłem groupby i nie konwertuje do listy ani nie używa len, więc (myślę) to opóźni rozdzielczość każdej wartości, dopóki nie zostanie faktycznie użyta. Niestety żadna z dostępnych odpowiedzi (w tej chwili) nie wydaje się oferować tę odmianę.

Oczywiście, jeśli musisz obsłużyć każdy element po kolei nest a dla pętli nad 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

Moim szczególnym zainteresowaniem była potrzeba zużywania generatora do przesyłanie zmian w partiach do 1000 do interfejsu API gmail:

    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)
 3
Author: John Mee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-18 08:48:46

Z NumPy to proste:

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

Wyjście:

1 2
3 4
5 6
7 8
 2
Author: endolith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-19 04:09:38
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()
 2
Author: Kamil Sindi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-20 11:14:58

O ile czegoś nie przeoczyłem, poniższe proste rozwiązanie z wyrażeniami generatora nie zostało wymienione. Zakłada się, że zarówno wielkość, jak i liczba kawałków są znane (co często ma miejsce) i że nie jest wymagane wypełnienie:

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))
 2
Author: Alexey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-27 16:55:34

W twojej drugiej metodzie, chciałbym przejść do następnej grupy 4, robiąc to:

ints = ints[4:]

Jednak nie zrobiłem żadnego pomiaru wydajności, więc nie wiem, który z nich może być bardziej wydajny.

Powiedziawszy to, zwykle wybierałbym pierwszą metodę. To nie jest ładne, ale często jest to konsekwencja interakcji ze światem zewnętrznym.

 1
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-12 02:59:16

Jeszcze jedna odpowiedź, której zalety to:

1) łatwo zrozumiałe
2) działa na dowolnych iterowalnych, nie tylko sekwencjach (niektóre z powyższych odpowiedzi zadławią filehandle)
3) nie ładuje fragmentu do pamięci na raz
4) nie tworzy listy odwołań do tego samego iteratora w pamięci
5) brak wypełnienia wartości wypełnienia na końcu listy

To powiedziawszy, nie mierzyłem czasu, więc może być wolniejszy niż niektóre mądrzejsze metody, a niektóre z zalet mogą być nieistotne, biorąc pod uwagę przypadek użycia.

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:
Kilka wad ze względu na to, że pętle wewnętrzne i zewnętrzne pobierają wartości z tego samego iteratora:
1) kontynuuj nie działa zgodnie z oczekiwaniami w zewnętrznej pętli - po prostu kontynuuje do następnego elementu, zamiast pomijać kawałek. Jednak nie wydaje się to problemem, ponieważ nie ma nic do przetestowania w zewnętrznej pętli.
2) przerwa nie działa zgodnie z oczekiwaniami w pętli wewnętrznej - control zakończy się ponownie w pętli wewnętrznej z następnym elementem w iteratorze. Aby pominąć całe fragmenty, należy owinąć wewnętrzny iterator (II powyżej) krotką, np. for c in tuple(ii), lub ustawić flagę i wydłużyć iterator.

 1
Author: elhefe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-12 02:22:56
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
Author: Wilfred Hughes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-20 11:45:37

Możesz użyć partycji lub części funkcji z funcy biblioteki:

from funcy import partition

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

Funkcje te mają również wersje iteratora ipartition i ichunks, które w tym przypadku będą bardziej wydajne.

Możesz również zerknąć na ich implementację .

 1
Author: Suor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-04 20:13:23

Jednoliniowy, adhoc roztwór do iteracji nad listą x w kawałkach o rozmiarze 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 ...
 1
Author: Tutul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-01 12:44:40

O rozwiązaniu podanym przez J.F. Sebastian tutaj :

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

To sprytne, ale ma jedną wadę - zawsze zwracaj krotkę. Jak zamiast tego zdobyć sznurek?
Oczywiście możesz napisać ''.join(chunker(...)), ale tymczasowa krotka i tak jest skonstruowana.

Możesz pozbyć się tymczasowej krotki, pisząc własne zip, tak:

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

Then

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

Przykładowe użycie:

>>> 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
Author: GingerPlusPlus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:14

Podoba mi się to podejście. Wydaje się prosty i nie magiczny i obsługuje wszystkie typy iteracyjne i nie wymaga importu.

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
Author: BallpointBen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-30 02:05:12

Całkiem tutaj pythoniczne (można też wstawić ciało split_groups funkcji)

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
 1
Author: Andrey Cizov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-06 22:20:50

Nigdy nie chcę, aby moje kawałki były wyściełane, więc ten wymóg jest niezbędny. Uważam, że możliwość pracy na dowolnym iterable jest również wymóg. Biorąc to pod uwagę, postanowiłem rozszerzyć na zaakceptowaną odpowiedź, https://stackoverflow.com/a/434411/1074659 .

Wydajność ma niewielki wpływ w tym podejściu, jeśli wyściełanie nie jest pożądane ze względu na konieczność porównania i filtrowania wartości wyściełanych. Jednak w przypadku dużych rozmiarów części to narzędzie jest bardzo wydajne.

#!/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
Author: frankish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-20 15:20:06

Oto chunker bez importu, który obsługuje Generatory:

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

Przykład użycia:

>>> 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')]
 1
Author: Cuadue,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-20 11:15:23