Python: Wie teilt man eine Liste in gleich große Stücke?

Eine Liste in Blöcke aufzuteilen braucht man ständig — API-Aufrufe die 100 IDs auf einmal nehmen, Datenbank-Inserts, Ratenbegrenzungen. Seit Python 3.12 gibt es dafür eine Funktion in der Standardbibliothek, und die meisten Antworten die man findet sind älter.

Die Antwort ab Python 3.12

from itertools import batched

for block in batched(daten, 3):
    ...
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list(batched(data, 3)) : [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10,)]

itertools.batched liefert Tupel, und das letzte ist kürzer, wenn die Eingabe nicht glatt aufgeht. Wenn du Listen willst, musst du das sagen:

as lists : [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

Es arbeitet verzögert, es wurde also nichts über den angeforderten Block hinaus berechnet:

type(batched(...)) : batched
first batch only   : (1, 2, 3)

Python 3.13 hat strict= ergänzt. Damit wird ein zu kurzer letzter Block abgelehnt, statt ihn zurückzugeben:

batched(data, 3, strict=True) -> ValueError: batched(): incomplete batch
batched(data, 5, strict=True) : [(1, 2, 3, 4, 5), (6, 7, 8, 9, 10)]

Nützlich, wenn ein kurzer Block bedeuten würde, dass die Eingabe kaputt ist.

Für ältere Versionen: Slicing

def bloecke(folge, n):
    for i in range(0, len(folge), n):
        yield folge[i:i + n]
list(chunks(data, 3)) : [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

Zwei Details machen das kürzer, als man erwarten würde. range mit Schrittweite liefert genau die Startindizes, die man braucht:

range(0, 10, 3) -> [0, 3, 6, 9]

Und Slices werden begrenzt statt zu werfen, das Ende braucht also keine Sonderbehandlung:

data[9:12] : [10]   <- no IndexError, slices clamp

Diese Variante erhält außerdem den Typ der Folge. Bei einem String bekommst du Strings zurück, wo batched dir Tupel aus einzelnen Zeichen geben würde.

Der Haken ist len(): Das funktioniert nur mit etwas Sliceable, also nicht mit einem Generator.

Das alte Rezept liefert etwas anderes

Dieses hier findest du in den Antworten von vor 3.12, aus der itertools-Dokumentation:

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)
grouper(data, 3)              : [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, None, None)]
grouper(data, 3, fillvalue=0) : [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 0, 0)]

Sieh dir die letzte Gruppe an. Sie ist auf volle Länge aufgefüllt, während batched sie kurz gelassen hat. Das ist ein anderes Ergebnis und nicht bloß eine andere Schreibweise — wer beim Modernisieren von altem Code das eine gegen das andere tauscht, schiebt anschließend zwei None-Werte in das, was die Blöcke weiterverarbeitet.

Den Trick [iter(iterable)] * n sollte man verstanden haben, denn er sieht aus, als könne er nicht funktionieren:

[iter(x)] * 2 makes two references to ONE iterator: True

Eine Liste zu multiplizieren wiederholt die Referenz, alle n Einträge sind also derselbe Iterator. zip zieht dann reihum daraus, und jede Runde nimmt damit die nächsten n Elemente.

Wenn du einen Generator hast

len() steht nicht zur Verfügung, die Slicing-Variante fällt also aus:

len(a generator) -> TypeError: object of type 'generator' has no len()

batched kommt damit zurecht, weil es ohnehin nur vorwärts zieht:

batched works anyway : [[1, 2, 3], [4, 5, 6], [7]]

Vor 3.12 nimmt man dafür islice:

def bloecke_islice(iterable, n):
    it = iter(iterable)
    while block := list(islice(it, n)):
        yield block
islice version : [[1, 2, 3], [4, 5, 6], [7]]

Beachte, dass it = iter(iterable) außerhalb der Schleife tragend ist — ein frischer Iterator in jedem Durchlauf würde ewig die ersten n Elemente liefern.

n Teile statt Teile zu n

Eine andere Frage, die im selben Zusammenhang gestellt wird: in eine feste Anzahl von Stücken aufteilen.

def in_n_teile(folge, teile):
    k, m = divmod(len(folge), teile)
    return [folge[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(teile)]
10 items into 3 parts : [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
10 items into 4 parts : [[1, 2, 3], [4, 5, 6], [7, 8], [9, 10]]

Die Größen unterscheiden sich um höchstens eins, und die größeren Stücke kommen zuerst. Genau das macht numpy.array_split — nur eben ohne numpy.

Randfälle

batched([], 3)    : []
batched(data, 20) : [(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
batched(data, 0)  -> ValueError: n must be at least one

Eine leere Eingabe liefert gar keinen Block statt einen leeren, und eine Blockgröße über der Eingabelänge liefert einen kurzen Block. Beides ist das, was man will, und beides ist nicht offensichtlich genug, um es zu raten.

Hinweis zu Netcup (Werbung)

Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).