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