W3docs

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 itertools

Warum 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 60

count 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 red

Praktische 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

FunktionReihenfolge relevant?Wiederholungen erlaubt?Anzahl (n=4, r=2)
productJaJan^r = 16
permutationsJaNeinn!/(n-r)! = 12
combinationsNeinNeinC(n,r) = 6
combinations_with_replacementNeinJaC(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

KategorieFunktionBeschreibung
Unendlichcount(start, step)Gleichmäßig verteilte Zahlen ohne Ende
Unendlichcycle(iterable)Iterable-Elemente endlos wiederholen
Unendlichrepeat(obj, n)obj genau n-mal liefern (oder endlos)
Kombinatorischproduct(*its, repeat)Kartesisches Produkt
Kombinatorischpermutations(it, r)Geordnete Anordnungen ohne Wiederholung
Kombinatorischcombinations(it, r)Ungeordnete Teilmengen ohne Wiederholung
Kombinatorischcombinations_with_replacement(it, r)Ungeordnete Teilmengen mit Wiederholung
Terminierendchain(*its)Iterables verketten
Terminierendchain.from_iterable(it)Eine Verschachtelungsebene abflachen
Terminierendislice(it, stop)Einen Iterator schneiden
Terminierendgroupby(it, key)Aufeinanderfolgende Elemente mit gleichem Schlüssel gruppieren
Terminierendcompress(data, sel)Nach boolescher Maske filtern
Terminierendfilterfalse(pred, it)Elemente behalten, bei denen Prädikat False ist
Terminierendtakewhile(pred, it)Liefern, solange Prädikat True ist, dann stoppen
Terminierenddropwhile(pred, it)Überspringen, solange Prädikat True ist, dann liefern
Terminierendstarmap(func, it)Map mit Argument-Entpackung
Terminierendzip_longest(*its, fill)Zip mit Auffüllung kürzerer Iterables
Terminierendaccumulate(it, func)Laufende Aggregation
Terminierendpairwise(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.

Übungen

Übung
Which itertools function would you use to stop consuming a generator the moment a condition becomes False?
Which itertools function would you use to stop consuming a generator the moment a condition becomes False?
Übung
What is the critical requirement before calling itertools.groupby() if you want all matching elements to end up in the same group?
What is the critical requirement before calling itertools.groupby() if you want all matching elements to end up in the same group?
Übung
Which itertools function produces the Cartesian product of two iterables?
Which itertools function produces the Cartesian product of two iterables?
Übung
You call itertools.combinations('ABCD', 2). How many tuples does the result contain?
You call itertools.combinations('ABCD', 2). How many tuples does the result contain?
Was this page helpful?