Python Iteratoren
Lerne, wie Python-Iteratoren funktionieren, eigene Iterator-Klassen erstellt werden und wann Iteratoren sinnvoller als Listen sind.
Ein Iterator ist eine der grundlegendsten Abstraktionen in Python. Immer wenn du eine for-Schleife schreibst, zip() aufrufst oder einen List Comprehension verwendest, verlässt sich Python im Hintergrund stillschweigend auf das Iterator-Protokoll. Dieses Kapitel erklärt, was Iteratoren sind, wie du eigene erstellst, wie du die umfangreichen eingebauten Iteratoren nutzt und wann Iteratoren das richtige Werkzeug sind.
Was ist ein Iterator?
Python unterscheidet zwischen zwei verwandten Konzepten:
- Ein Iterable ist jedes Objekt, über das du iterieren kannst — eine
list,tuple,str,dict,setoder ein Objekt, dessen Klasse__iter__definiert. Es kann einen Iterator erzeugen, verfolgt aber nicht selbst die Position. - Ein Iterator ist ein Objekt, das den Durchlaufzustand verfolgt. Es implementiert zwei Methoden, die zusammen das Iterator-Protokoll bilden:
__iter__()— gibt das Iterator-Objekt selbst zurück. Das ermöglicht es Iteratoren, infor-Schleifen und anderen Iterationskontexten zu funktionieren.__next__()— gibt beim jeden Aufruf den nächsten Wert zurück. Wenn keine Werte mehr vorhanden sind, wirdStopIterationausgelöst.
Der wesentliche Unterschied: Du kannst über eine Liste so oft iterieren, wie du möchtest, weil jede for-Schleife einen neuen Iterator anfordert. Ein Iterator ist einweg und einmalig — sobald er erschöpft ist, löst ein Aufruf von next() stets StopIteration aus.
graph LR
A[Iterator Object] --> B[__iter__]
B --> C[Returns self]
A --> D[__next__]
D --> E[Next Value]
D --> F{No values left?}
F -->|Yes| G[Raises StopIteration]
F -->|No| EWie eine for-Schleife Iteratoren verwendet
Die for-Schleife ist nur syntaktischer Zucker für das Iterator-Protokoll. Intern übersetzt Python Folgendes:
for item in some_iterable:
print(item)in ungefähr das hier:
_it = iter(some_iterable) # call __iter__()
while True:
try:
item = next(_it) # call __next__()
except StopIteration:
break
print(item)Das Verständnis dieser Übersetzung macht deutlich, warum jedes Objekt, das __iter__ und __next__ implementiert, nahtlos in einer for-Schleife, mit zip(), enumerate() und jedem anderen Kontext, der ein Iterable erwartet, funktioniert.
Einen eigenen Iterator erstellen
Um einen eigenen Iterator zu erstellen, definiere eine Klasse, die sowohl __iter__ als auch __next__ implementiert. Hier ist ein Countdown-Iterator, der von einer gegebenen Zahl bis 1 herunterzählt:
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self # the iterator is its own iterable
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for n in Countdown(5):
print(n)
# Output:
# 5
# 4
# 3
# 2
# 1Beachte, dass __iter__ self zurückgibt. Das ermöglicht es, dasselbe Objekt direkt in einer for-Schleife zu platzieren — die Schleife ruft iter() darauf auf, was __iter__() aufruft, das wiederum den Iterator selbst zurückgibt.
Einen Schritt-Parameter hinzufügen
Du kannst innerhalb von __next__ jede beliebige Logik hinzufügen. Hier ist ein StepRange-Iterator, der range() nachahmt, aber einen Schrittwert akzeptiert:
class StepRange:
def __init__(self, start, stop, step=1):
self.current = start
self.stop = stop
self.step = step
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += self.step
return value
print(list(StepRange(0, 10, 3)))
# Output: [0, 3, 6, 9]Der Aufruf von list() auf einem Iterator erschöpft ihn und sammelt alle Werte in einer Liste — ein nützliches Muster, wenn du alle Ergebnisse auf einmal benötigst.
Die eingebauten Funktionen iter() und next()
Die eingebauten Funktionen iter() und next() sind der Standardweg, um direkt mit dem Iterator-Protokoll zu arbeiten.
iter(obj)— ruftobj.__iter__()auf und gibt den resultierenden Iterator zurück.next(it)— ruftit.__next__()auf und gibt den nächsten Wert zurück.next(it, default)— gibtdefaultzurück, anstattStopIterationauszulösen, wenn der Iterator erschöpft ist. Das ist die sicherste Methode, einen Blick auf das nächste Element zu werfen, ohne einen try/except-Block zu verwenden.
words = ["hello", "world"]
it = iter(words)
print(next(it)) # hello
print(next(it)) # world
print(next(it, "done")) # done (exhausted; returns default)Die Zwei-Argument-Form von next() ist besonders nützlich in Streaming- oder Parsing-Szenarien, in denen du das Ende der Eingabe elegant behandeln möchtest.
Iteratoren sind einmalig verwendbar
Das ist der häufigste Fallstrick bei Iteratoren: Sobald ein Iterator erschöpft ist, kann er nicht zurückgespult werden.
it = iter([1, 2, 3])
for x in it:
print(x) # prints 1, 2, 3
for x in it:
print(x) # prints nothing — iterator is exhaustedWenn du mehrfach iterieren musst, behalte das ursprüngliche Iterable (z. B. die Liste) und rufe iter() erneut auf, oder verwende einen List Comprehension, um alle Werte zuerst zu materialisieren.
Eingebaute Funktionen, die Iteratoren zurückgeben
Die Standardbibliothek von Python ist auf Iteratoren aufgebaut. Diese Funktionen geben alle Iteratoren statt Listen zurück, sind also auch bei sehr großen Sequenzen speichereffizient:
range()
range(start, stop, step) gibt einen Iterator von ganzen Zahlen zurück. Die ganzen Zahlen werden nicht im Speicher abgelegt — jede wird bei Bedarf berechnet.
for i in range(1, 6):
print(i)
# Output: 1 2 3 4 5zip()
zip() nimmt mehrere Iterables und gibt einen Iterator von Tupeln zurück, der Elemente positionsweise zusammenführt. Die Iteration endet beim kürzesten Eingabe-Iterable.
names = ["Alice", "Bob", "Carol"]
scores = [95, 88, 72]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Output:
# Alice: 95
# Bob: 88
# Carol: 72enumerate()
enumerate() umhüllt ein beliebiges Iterable und gibt (Index, Wert)-Paare zurück. Verwende es, um eine manuelle Zählervariable zu vermeiden.
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# Output:
# 1. apple
# 2. banana
# 3. cherrymap() und filter()
Beide Funktionen geben Iteratoren zurück (in Python 3). map(fn, iterable) wendet eine Funktion auf jedes Element an; filter(fn, iterable) behält nur Elemente, für die die Funktion True zurückgibt.
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
print(doubled) # [2, 4, 6, 8, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]Prüfen, ob ein Objekt ein Iterator ist
Verwende isinstance() mit den abstrakten Basisklassen aus dem Modul collections.abc, um auf Iterierbarkeit und Iterator-Status zu prüfen:
from collections.abc import Iterable, Iterator
my_list = [1, 2, 3]
my_iter = iter(my_list)
print(isinstance(my_list, Iterable)) # True — list is iterable
print(isinstance(my_list, Iterator)) # False — list is NOT an iterator
print(isinstance(my_iter, Iterator)) # True — list_iterator is an iterator
print(isinstance(my_iter, Iterable)) # True — all iterators are also iterablesJeder Iterator ist auch ein Iterable (weil __iter__ self zurückgibt), aber nicht jedes Iterable ist ein Iterator.
Wann Iteratoren statt Listen verwenden
| Situation | Verwende |
|---|---|
Zufälliger Zugriff erforderlich (items[5]) | list |
| Einmaliges Iterieren, Speicher ist wichtig | Iterator / Generator |
| Unendliche oder sehr große Sequenzen | Iterator / Generator |
| Mehrfaches Iterieren erforderlich | list (Original behalten) |
| Pipeline von Transformationen | verkettete Iteratoren (map, filter, itertools) |
Bei großen Datensätzen — Millionen von Zeilen aus einer Datei lesen, Streaming-Daten verarbeiten — vermeidet ein Iterator das Laden aller Daten auf einmal in den Speicher. Bei kleinen, endlichen Sammlungen, auf deren Elemente du wiederholt zugreifst, ist eine Liste einfacher.
Iteratoren vs. Generatoren
Ein Generator ist eine komfortable Abkürzung zum Schreiben eines Iterators. Statt einer Klasse mit __iter__ und __next__ schreibst du eine Funktion, die yield verwendet. Python wandelt sie automatisch in einen Iterator um.
# Iterator class
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
# Equivalent generator function
def countdown(start):
while start > 0:
yield start
start -= 1
print(list(countdown(5))) # [5, 4, 3, 2, 1]Verwende einen klassenbasierten Iterator, wenn du zusätzliche Methoden oder veränderbaren Zustand benötigst, der über das hinausgeht, was ein einfacher Generator bietet. Verwende einen Generator in den meisten anderen Fällen — er ist prägnanter und gleich leistungsfähig.
Weitere Informationen zu yield, Generator-Ausdrücken und send() findest du im Kapitel Python Generatoren.