Python itertools-Modul
Python itertools meistern: unendliche Iteratoren, Kombinatorik, Gruppierung, Verkettung und Filterung – mit klaren, ausführbaren Beispielen.
Das itertools-Modul von Python ist ein Werkzeugkasten aus der Standardbibliothek mit schnellen, speichereffizienten Bausteinen für die Arbeit mit Iteratoren. Jede Funktion in itertools gibt einen Iterator zurück – sie erzeugt Werte bei Bedarf, anstatt eine Liste im Speicher aufzubauen – was das Modul ideal für große Datensätze, unendliche Sequenzen und zusammensetzbare Datenpipelines macht.
Dieses Kapitel behandelt alle drei Kategorien von itertools-Funktionen: unendliche Iteratoren (count, cycle, repeat), kombinatorische Iteratoren (product, permutations, combinations, combinations_with_replacement) und terminierende Iteratoren (chain, islice, groupby, compress, filterfalse, takewhile, dropwhile, starmap, zip_longest, accumulate, pairwise).
Eine Installation ist nicht notwendig – itertools wird mit jeder Python 3-Installation mitgeliefert:
import itertoolsWarum itertools?
Stellen Sie sich vor, Sie möchten die ersten 10 Vielfachen einer Zahl lesen. Ohne itertools benötigen Sie eine Liste oder einen manuellen Zähler. Mit itertools.count und itertools.islice ist die Absicht sofort klar und der Speicherverbrauch bleibt konstant:
import itertools
multiples = itertools.islice(itertools.count(0, 7), 10)
print(list(multiples))
# [0, 7, 14, 21, 28, 35, 42, 49, 56, 63]Die Philosophie von itertools: Bauen Sie ein kleines, korrektes Stück, und kombinieren Sie es dann mit anderen. Zwei itertools-Funktionen zu verketten ist schneller und weniger fehleranfällig als die entsprechende Schleife von Hand zu schreiben.
Unendliche Iteratoren
Diese Iteratoren erzeugen Werte auf unbestimmte Zeit. Kombinieren Sie sie immer mit islice, einem for … break oder einem anderen terminierenden Mechanismus, um eine Endlosschleife zu vermeiden.
count(start=0, step=1)
count erzeugt eine gleichmäßig verteilte Zahlenfolge. Es entspricht im Wesentlichen range ohne Obergrenze und mit Unterstützung für Fließkommazahlen und negative Schrittweiten.
import itertools
# Integer counter
for n in itertools.islice(itertools.count(10), 5):
print(n, end=' ')
# 10 11 12 13 14
print()
# Float step
for n in itertools.islice(itertools.count(0.0, 0.5), 5):
print(n, end=' ')
# 0.0 0.5 1.0 1.5 2.0
print()
# Countdown
for n in itertools.islice(itertools.count(100, -10), 5):
print(n, end=' ')
# 100 90 80 70 60count ist nützlich, wenn Sie Elemente eines Iterables nummerieren müssen, ohne im Voraus zu wissen, wie viele es sind – das enumerate-Idiom, aber mit benutzerdefiniertem Start und Schrittweite.
cycle(iterable)
cycle wiederholt die Elemente eines beliebigen Iterables auf unbestimmte Zeit.
import itertools
colours = itertools.cycle(['red', 'green', 'blue'])
for i, colour in enumerate(colours):
if i == 7:
break
print(colour, end=' ')
# red green blue red green blue redPraktische Verwendung – Elemente nach dem Round-Robin-Prinzip Teams zuweisen:
import itertools
teams = itertools.cycle(['Alpha', 'Beta', 'Gamma'])
players = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve']
assignments = {player: team for player, team in zip(players, teams)}
print(assignments)
# {'Alice': 'Alpha', 'Bob': 'Beta', 'Carol': 'Gamma', 'Dave': 'Alpha', 'Eve': 'Beta'}repeat(object, times=None)
repeat gibt dasselbe object genau times-mal zurück (oder unbegrenzt, wenn times weggelassen wird).
import itertools
# Finite repeat
print(list(itertools.repeat('hello', 3)))
# ['hello', 'hello', 'hello']
# Used as a fixed argument supplier in map()
squares = list(map(pow, range(1, 6), itertools.repeat(2)))
print(squares)
# [1, 4, 9, 16, 25]Das Muster map(pow, range(1, 6), repeat(2)) ist ein gebräuchliches Idiom, um einer zweistelligen Funktion ein konstantes zweites Argument zu übergeben.
Kombinatorische Iteratoren
Diese Iteratoren erzeugen alle Kombinationen, Permutationen oder Kreuzprodukte eines Eingabe-Iterables. Sie sind unverzichtbar für Brute-Force-Suchen, Testfallerzeugung und kombinatorische Probleme.
product(*iterables, repeat=1)
product berechnet das kartesische Produkt – alle geordneten Kombinationen, bei denen je ein Element aus jedem Iterable entnommen wird. Es entspricht verschachtelten for-Schleifen.
import itertools
suits = ['Hearts', 'Diamonds']
ranks = ['A', 'K', 'Q']
deck = list(itertools.product(suits, ranks))
print(deck)
# [('Hearts', 'A'), ('Hearts', 'K'), ('Hearts', 'Q'),
# ('Diamonds', 'A'), ('Diamonds', 'K'), ('Diamonds', 'Q')]Verwenden Sie repeat, um das Produkt eines Iterables mit sich selbst mehrfach zu berechnen:
import itertools
# All 2-digit binary numbers
binary_pairs = list(itertools.product([0, 1], repeat=2))
print(binary_pairs)
# [(0, 0), (0, 1), (1, 0), (1, 1)]Achtung: product lädt die Eingabe-Iterables vollständig in den Speicher (um mehrere Durchläufe zu ermöglichen), übergeben Sie daher keine sehr großen Iteratoren als Eingabe.
permutations(iterable, r=None)
permutations erzeugt alle geordneten Anordnungen von r Elementen aus der Eingabe. Wenn r weggelassen wird, werden alle Elemente verwendet.
import itertools
# All orderings of 3 letters
perms = list(itertools.permutations('ABC'))
print(perms)
# [('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'),
# ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]
print(len(perms)) # 6 (3! = 6)
# 2-element permutations
perms2 = list(itertools.permutations('ABC', 2))
print(perms2)
# [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
print(len(perms2)) # 6 (3 * 2 = 6)Bei Permutationen kommt es auf die Reihenfolge an – ('A', 'B') und ('B', 'A') sind unterschiedliche Ergebnisse.
combinations(iterable, r)
combinations erzeugt alle ungeordneten Auswahlen von r Elementen. Im Gegensatz zu permutations spielt die Reihenfolge keine Rolle – jede Teilmenge erscheint nur einmal.
import itertools
# All 2-element subsets of [1, 2, 3, 4]
combos = list(itertools.combinations([1, 2, 3, 4], 2))
print(combos)
# [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
print(len(combos)) # 6 (C(4,2) = 6)Ein häufiger Anwendungsfall – alle Paare von Elementen auf eine Eigenschaft prüfen:
import itertools
words = ['bat', 'tab', 'cat', 'tac']
anagram_pairs = [
(a, b) for a, b in itertools.combinations(words, 2)
if sorted(a) == sorted(b)
]
print(anagram_pairs)
# [('bat', 'tab'), ('cat', 'tac')]combinations_with_replacement(iterable, r)
Wie combinations, aber jedes Element darf in einer Auswahl mehr als einmal vorkommen.
import itertools
# All 2-element combinations with repetition from [1, 2, 3]
combos = list(itertools.combinations_with_replacement([1, 2, 3], 2))
print(combos)
# [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]Dies ist nützlich, um alle möglichen Würfelergebnisse, Münzwurf-Sequenzen oder Zeichenauswahlen für Passwörter zu generieren.
Kombinatorische Funktionen auf einen Blick
| Funktion | Reihenfolge relevant? | Wiederholungen erlaubt? | Anzahl (n=4, r=2) |
|---|---|---|---|
product | Ja | Ja | n^r = 16 |
permutations | Ja | Nein | n!/(n-r)! = 12 |
combinations | Nein | Nein | C(n,r) = 6 |
combinations_with_replacement | Nein | Ja | C(n+r-1,r) = 10 |
Terminierende Iteratoren
Terminierende Iteratoren verarbeiten eine endliche Eingabe und enden, wenn diese erschöpft ist.
chain(*iterables)
chain behandelt mehrere Iterables als eine einzige zusammenhängende Sequenz, ohne eine neue Liste aufzubauen.
import itertools
a = [1, 2, 3]
b = (4, 5)
c = range(6, 9)
combined = list(itertools.chain(a, b, c))
print(combined)
# [1, 2, 3, 4, 5, 6, 7, 8]chain.from_iterable akzeptiert ein einzelnes Iterable von Iterables – nützlich, wenn die Anzahl der Sequenzen nicht im Voraus bekannt ist:
import itertools
nested = [[1, 2], [3, 4], [5, 6]]
flat = list(itertools.chain.from_iterable(nested))
print(flat)
# [1, 2, 3, 4, 5, 6]Dies ist eine schnelle, speichereffiziente Alternative zu [item for sublist in nested for item in sublist].
islice(iterable, stop) / islice(iterable, start, stop, step=1)
islice schneidet jeden Iterator – einschließlich unendlicher – ab, ohne ihn vollständig zu materialisieren. Die Argumente entsprechen Pythons slice-Notation, akzeptieren jedoch nur nicht-negative ganze Zahlen.
import itertools
# First 5 elements
print(list(itertools.islice(range(100), 5)))
# [0, 1, 2, 3, 4]
# Elements 10–14 (start inclusive, stop exclusive)
print(list(itertools.islice(range(100), 10, 15)))
# [10, 11, 12, 13, 14]
# Every other element from position 0 to 10
print(list(itertools.islice(range(20), 0, 10, 2)))
# [0, 2, 4, 6, 8]islice unterstützt keine negativen Indizes oder negativen Schrittweiten (anders als das reguläre Listen-Slicing).
groupby(iterable, key=None)
groupby gruppiert aufeinanderfolgende Elemente mit demselben Schlüsselwert. Es gibt (key, group_iterator)-Paare zurück.
import itertools
data = [
('fruit', 'apple'),
('fruit', 'banana'),
('veggie', 'carrot'),
('veggie', 'broccoli'),
('fruit', 'cherry'),
]
for category, group in itertools.groupby(data, key=lambda x: x[0]):
items = [item[1] for item in group]
print(f'{category}: {items}')
# fruit: ['apple', 'banana']
# veggie: ['carrot', 'broccoli']
# fruit: ['cherry']Wichtiger Hinweis: groupby gruppiert nur aufeinanderfolgende gleiche Elemente. Wenn die Daten nicht nach dem Schlüssel vorsortiert sind, bilden ähnliche Elemente an unterschiedlichen Positionen separate Gruppen (wie oben gezeigt – 'cherry' startet eine neue 'fruit'-Gruppe, anstatt der ersten beizutreten). Sortieren Sie die Daten immer nach dem Schlüssel, bevor Sie groupby aufrufen:
import itertools
data = [
('fruit', 'apple'),
('veggie', 'carrot'),
('fruit', 'banana'),
('veggie', 'broccoli'),
('fruit', 'cherry'),
]
# Sort first, then group
sorted_data = sorted(data, key=lambda x: x[0])
for category, group in itertools.groupby(sorted_data, key=lambda x: x[0]):
items = [item[1] for item in group]
print(f'{category}: {items}')
# fruit: ['apple', 'banana', 'cherry']
# veggie: ['carrot', 'broccoli']Beachten Sie außerdem, dass der Gruppen-Iterator ungültig wird, sobald Sie zum nächsten Schlüssel übergehen – konsumieren Sie jede Gruppe, bevor Sie next() auf dem äußeren Iterator aufrufen.
compress(data, selectors)
compress filtert data, indem nur die Elemente beibehalten werden, deren entsprechender selector-Wert truthy ist.
import itertools
names = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve']
active = [True, False, True, True, False]
result = list(itertools.compress(names, active))
print(result)
# ['Alice', 'Carol', 'Dave']compress entspricht [d for d, s in zip(data, selectors) if s], ist jedoch schneller und vermeidet die Zwischenliste.
filterfalse(predicate, iterable)
filterfalse ist das Komplement des eingebauten filter – es liefert Elemente, für die das Prädikat False zurückgibt.
import itertools
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Keep only odd numbers (those that fail the even test)
odds = list(itertools.filterfalse(lambda x: x % 2 == 0, numbers))
print(odds)
# [1, 3, 5, 7, 9]takewhile(predicate, iterable)
takewhile liefert Elemente, solange das Prädikat True ist, und hält dann sofort an – auch wenn spätere Elemente das Prädikat erfüllen würden.
import itertools
data = [2, 4, 6, 3, 8, 10]
# Stop as soon as an odd number appears
evens_from_start = list(itertools.takewhile(lambda x: x % 2 == 0, data))
print(evens_from_start)
# [2, 4, 6]dropwhile(predicate, iterable)
dropwhile ist das Gegenstück zu takewhile: Es überspringt Elemente, solange das Prädikat True ist, und liefert dann alle verbleibenden Elemente (einschließlich derer, bei denen das Prädikat wieder True wäre).
import itertools
data = [2, 4, 6, 3, 8, 10]
# Drop leading even numbers, yield everything from the first odd onward
result = list(itertools.dropwhile(lambda x: x % 2 == 0, data))
print(result)
# [3, 8, 10]takewhile und dropwhile sind nützlich bei der Verarbeitung von Log-Dateien oder Streams, bei denen Sie einen Header-Abschnitt überspringen oder bei einer Sentinel-Zeile stoppen möchten.
starmap(function, iterable)
starmap wendet eine Funktion auf jedes Element eines Iterables an und entpackt das Element dabei als Positionsargumente. Es entspricht map, aber für Iterables aus Tupeln.
import itertools
pairs = [(2, 3), (4, 2), (10, 3)]
results = list(itertools.starmap(pow, pairs))
print(results)
# [8, 16, 1000]Im Vergleich zu map(pow, [2, 4, 10], [3, 2, 3]) – starmap funktioniert, wenn die Argumente bereits als Tupel gebündelt sind.
zip_longest(*iterables, fillvalue=None)
Das eingebaute zip endet beim kürzesten Iterable. zip_longest füllt kürzere Iterables mit fillvalue auf, sodass alle Iterables vollständig verarbeitet werden.
import itertools
a = [1, 2, 3]
b = ['a', 'b', 'c', 'd', 'e']
print(list(zip(a, b)))
# [(1, 'a'), (2, 'b'), (3, 'c')] — b's 'd' and 'e' are lost
print(list(itertools.zip_longest(a, b, fillvalue=0)))
# [(1, 'a'), (2, 'b'), (3, 'c'), (0, 'd'), (0, 'e')]accumulate(iterable, func=operator.add, *, initial=None)
accumulate berechnet laufende Gesamtsummen (oder beliebige andere laufende Aggregationen). Standardmäßig wird addiert, aber Sie können jede zweistellige Funktion übergeben.
import itertools
import operator
numbers = [1, 2, 3, 4, 5]
# Running sum (default)
print(list(itertools.accumulate(numbers)))
# [1, 3, 6, 10, 15]
# Running product
print(list(itertools.accumulate(numbers, operator.mul)))
# [1, 2, 6, 24, 120]
# Running maximum
data = [3, 1, 4, 1, 5, 9, 2, 6]
print(list(itertools.accumulate(data, max)))
# [3, 3, 4, 4, 5, 9, 9, 9]Der initial-Parameter (Python 3.8+) fügt vor dem ersten Element einen Startwert ein:
import itertools
print(list(itertools.accumulate([1, 2, 3], initial=100)))
# [100, 101, 103, 106]pairwise(iterable)
pairwise (Python 3.10+) liefert aufeinanderfolgende überlappende Paare aus dem Iterable.
import itertools
data = [1, 2, 3, 4, 5]
print(list(itertools.pairwise(data)))
# [(1, 2), (2, 3), (3, 4), (4, 5)]Dies ist nützlich für die Berechnung von Differenzen zwischen aufeinanderfolgenden Werten oder für Sliding-Window-Logik mit einer Fenstergröße von genau 2:
import itertools
prices = [10.0, 12.5, 11.0, 13.5, 15.0]
changes = [b - a for a, b in itertools.pairwise(prices)]
print(changes)
# [2.5, -1.5, 2.5, 1.5]Vor Python 3.10 war das Äquivalent zip(data, data[1:]) (funktioniert für Sequenzen) oder ein manueller tee-basierter Ansatz (funktioniert für beliebige Iteratoren).
itertools zu Pipelines kombinieren
Die wahre Stärke von itertools zeigt sich, wenn Sie Funktionen kombinieren. Da jede Funktion einen Iterator zurückgibt, können Sie sie ohne Zwischenlisten verketten.
Beispiel: Die 3 häufigsten Wörter in einem Text
import itertools
import operator
text = "the quick brown fox jumps over the lazy dog the fox"
words = text.split()
# Sort words so groupby can collect identical words together
sorted_words = sorted(words)
# Count each word using groupby
word_counts = (
(key, sum(1 for _ in group))
for key, group in itertools.groupby(sorted_words)
)
# Sort by count descending, take the top 3
top3 = list(itertools.islice(
sorted(word_counts, key=operator.itemgetter(1), reverse=True),
3
))
print(top3)
# [('the', 3), ('fox', 2), ('brown', 1)]Beispiel: Ein Iterable in Blöcke fester Größe aufteilen
import itertools
def batched(iterable, n):
"""Yield successive n-sized tuples from iterable."""
it = iter(iterable)
while chunk := tuple(itertools.islice(it, n)):
yield chunk
data = range(10)
for batch in batched(data, 3):
print(batch)
# (0, 1, 2)
# (3, 4, 5)
# (6, 7, 8)
# (9,)Python 3.12 liefert itertools.batched als eingebaute Funktion mit, sodass Sie den obigen Helfer durch itertools.batched(data, 3) in modernem Python ersetzen können.
Kurzreferenz
| Kategorie | Funktion | Beschreibung |
|---|---|---|
| Unendlich | count(start, step) | Gleichmäßig verteilte Zahlen ohne Ende |
| Unendlich | cycle(iterable) | Iterable-Elemente endlos wiederholen |
| Unendlich | repeat(obj, n) | obj genau n-mal liefern (oder endlos) |
| Kombinatorisch | product(*its, repeat) | Kartesisches Produkt |
| Kombinatorisch | permutations(it, r) | Geordnete Anordnungen ohne Wiederholung |
| Kombinatorisch | combinations(it, r) | Ungeordnete Teilmengen ohne Wiederholung |
| Kombinatorisch | combinations_with_replacement(it, r) | Ungeordnete Teilmengen mit Wiederholung |
| Terminierend | chain(*its) | Iterables verketten |
| Terminierend | chain.from_iterable(it) | Eine Verschachtelungsebene abflachen |
| Terminierend | islice(it, stop) | Einen Iterator schneiden |
| Terminierend | groupby(it, key) | Aufeinanderfolgende Elemente mit gleichem Schlüssel gruppieren |
| Terminierend | compress(data, sel) | Nach boolescher Maske filtern |
| Terminierend | filterfalse(pred, it) | Elemente behalten, bei denen Prädikat False ist |
| Terminierend | takewhile(pred, it) | Liefern, solange Prädikat True ist, dann stoppen |
| Terminierend | dropwhile(pred, it) | Überspringen, solange Prädikat True ist, dann liefern |
| Terminierend | starmap(func, it) | Map mit Argument-Entpackung |
| Terminierend | zip_longest(*its, fill) | Zip mit Auffüllung kürzerer Iterables |
| Terminierend | accumulate(it, func) | Laufende Aggregation |
| Terminierend | pairwise(it) | Aufeinanderfolgende überlappende Paare (3.10+) |
Für die Konzepte der Lazy-Auswertung hinter itertools siehe Python Generators und Python Iterators. Für funktionale Hilfsfunktionen, die itertools ergänzen, siehe Python Lambda Functions und das Python collections-Modul.