Zum Inhalt springen
aviral gupta

// I3.1 · ca. 32 Min. · Aufbau

Das Iterator-Protokoll

Nach dieser Lektion gehen Sie jedes Iterable mit iter() und next() von Hand durch, schreiben eine Klasse, die in einer for-Schleife funktioniert, und unterscheiden wiederverwendbare Iterables von Iteratoren für einen Durchlauf.

Lektion 1 von 6 in I3 Iteration und funktionale Werkzeuge

Anfang des Moduls

Danach können Sie

  • Ein Iterable mit iter() und next() von Hand durchgehen und sein Ende behandeln
  • Eine Iterator-Klasse mit __iter__ und __next__ schreiben, die StopIteration auslöst
  • Ein Iterable, das jedes Mal einen neuen Iterator liefert, von einem Iterator für einen Durchlauf unterscheiden
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Modul I2: Ein Objekt hält seinen Zustand in Attributen. Was gibt dieser Code aus?

    class Counter:
        def __init__(self, start):
            self.value = start
    
        def step(self):
            self.value -= 1
            return self.value
    
    c = Counter(3)
    print(c.step(), c.step())
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Was gibt dieser Code aus?

    numbers = [1, 2, 3]
    it = iter(numbers)
    print(list(it), list(it))
  3. Üben · Aufgabe 3 von 7

    Setzen Sie die Ausnahme ein, die einer for-Schleife oder list() sagt, dass der Countdown vorbei ist.

    class Countdown:
        def __init__(self, start):
            self.current = start
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.current <= 0:
                raise ____
            self.current -= 1
            return self.current + 1
    raise
  4. Üben · Aufgabe 4 von 7

    next() nimmt auch ein zweites Argument. Was gibt dieser Code aus?

    it = iter("ab")
    print(next(it), next(it), next(it, "-"))
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jedem Teil des Iterator-Protokolls zu, was er tut.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Dasselbe Objekt wird zweimal summiert. Was gibt dieser Code aus?

    class Numbers:
        def __init__(self):
            self.n = 0
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.n == 3:
                raise StopIteration
            self.n += 1
            return self.n
    
    nums = Numbers()
    print(sum(nums), sum(nums))
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie eine Iterator-Klasse Steps(start, stop, step), die für positive Schritte wie range arbeitet: Steps(0, 10, 3) liefert 0, 3, 6, 9. Geben Sie ihr __iter__ und __next__, und lösen Sie StopIteration aus, sobald der Wert stop erreicht. Geben Sie list(Steps(0, 10, 3)) und list(Steps(5, 5, 1)) aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Playlist, die sich immer wieder durchlaufen lässt

Playlist ist ein Iterable: Sein __iter__ liefert jedes Mal einen neuen PlaylistIterator. Der Iterator hält seine eigene Position in self.index. Das Programm holt zwei Songs von Hand mit next() und durchläuft dann die Playlist; dabei startet ein zweiter, unabhängiger Iterator von vorn. Der erste Iterator steht noch dort, wo er aufgehört hat.

main.py

class PlaylistIterator:
    """Steps through a playlist's songs, one per next()."""

    def __init__(self, songs: list[str]) -> None:
        self.songs = songs
        self.index = 0

    def __iter__(self) -> "PlaylistIterator":
        return self

    def __next__(self) -> str:
        if self.index >= len(self.songs):
            raise StopIteration
        song = self.songs[self.index]
        self.index += 1
        return song


class Playlist:
    """An iterable: every loop gets a fresh iterator."""

    def __init__(self, *songs: str) -> None:
        self.songs = list(songs)

    def __iter__(self) -> PlaylistIterator:
        return PlaylistIterator(self.songs)


playlist = Playlist("Intro", "Verse", "Outro")
it = iter(playlist)
print(next(it))
print(next(it))
for song in playlist:
    print("loop:", song)
print(next(it))
print(next(it, "end of playlist"))

Ausführen mit

python main.py

Ausgabe

Intro
Verse
loop: Intro
loop: Verse
loop: Outro
Outro
end of playlist
  • Die for-Schleife rief iter(playlist) auf und bekam einen eigenen Iterator, also begann sie wieder bei Intro.
  • it wurde von der Schleife nicht gestört: Sein nächster Song war immer noch Outro.
  • Das letzte next(it, …) fand it verbraucht und gab den Standardwert zurück, statt StopIteration auszulösen.
  • PlaylistIterator gibt aus __iter__ self zurück und ließe sich darum ebenfalls in einer for-Schleife verwenden.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 3

Ein Countdown-Iterator

Vervollständigen Sie die Iterator-Klasse Countdown in main.py. Countdown(3) liefert 3, 2 und 1, pro next() eine Zahl, und löst danach StopIteration aus, auch bei jedem weiteren Aufruf. Countdown(0) liefert nichts. __iter__ gibt schon self zurück; schreiben Sie __next__.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Prüfen Sie zuerst, ob noch etwas übrig ist: if self.current <= 0: raise StopIteration.

  2. Hinweis 2

    Merken Sie sich die aktuelle Zahl in einer Variablen, bevor Sie self.current ändern, damit Sie sie zurückgeben können.

  3. Hinweis 3

    number = self.current; self.current -= 1; return number. Weil self.current bei 0 bleibt, löst jeder spätere Aufruf erneut aus.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

class Countdown:
    """Counts down from start to 1, one number per next()."""

    def __init__(self, start: int) -> None:
        self.current = start

    def __iter__(self) -> "Countdown":
        return self

    def __next__(self) -> int:
        if self.current <= 0:
            raise StopIteration
        number = self.current
        self.current -= 1
        return number


if __name__ == "__main__":
    print(list(Countdown(3)))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

class Countdown:
    """Counts down from start to 1, one number per next()."""

    def __init__(self, start: int) -> None:
        self.current = start

    def __iter__(self) -> "Countdown":
        return self

    def __next__(self) -> int:
        # Raise StopIteration when the count is used up;
        # otherwise return the current number and step down.
        raise StopIteration


if __name__ == "__main__":
    print(list(Countdown(3)))

test_main.py

from main import Countdown


def test_counts_down():
    """Countdown(3) liefert 3, 2, 1"""
    got = list(Countdown(3))
    assert got == [3, 2, 1], f"list(Countdown(3)) lieferte {got!r}, erwartet: [3, 2, 1]"


def test_next_by_hand():
    """next() liefert pro Aufruf eine Zahl"""
    c = Countdown(2)
    got = next(c), next(c)
    assert got == (2, 1), f"zwei next()-Aufrufe lieferten {got!r}, erwartet: (2, 1)"


def test_is_its_own_iterator():
    """iter() eines Countdown ist der Countdown selbst"""
    c = Countdown(2)
    assert iter(c) is c, "iter(c) lieferte nicht c selbst: __iter__ muss self zurückgeben"


def test_stays_exhausted():
    """Nach der letzten Zahl löst next() weiter StopIteration aus"""
    c = Countdown(1)
    list(c)
    for _ in range(2):
        try:
            got = next(c)
        except StopIteration:
            continue
        raise AssertionError(f"next() auf einem verbrauchten Countdown lieferte {got!r}, statt StopIteration auszulösen")


def test_zero():
    """Countdown(0) liefert nichts"""
    got = list(Countdown(0))
    assert got == [], f"list(Countdown(0)) lieferte {got!r}, erwartet: []"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 3

Ein Satz, der sich zweimal durchlaufen lässt

Die Klasse Sentence im Starter ist ihr eigener Iterator, darum findet eine zweite Schleife über denselben Satz nichts. Machen Sie daraus ein wiederverwendbares Iterable: __iter__ liefert jedes Mal einen neuen Iterator über self.words, und die Klasse braucht weder __next__ noch index. Dann funktioniert list(s) zweimal, und auch zwei verschachtelte Schleifen über s.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Eine Liste weiß schon, wie sie neue Iteratoren herausgibt. Lassen Sie die Liste die Arbeit machen.

  2. Hinweis 2

    __iter__ kann iter(self.words) zurückgeben. Löschen Sie __next__ und self.index.

  3. Hinweis 3

    Für die Annotation importieren Sie Iterator aus collections.abc und schreiben -> Iterator[str].

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from collections.abc import Iterator


class Sentence:
    """The words of a text; loop over it as often as you like."""

    def __init__(self, text: str) -> None:
        self.words = text.split()

    def __iter__(self) -> Iterator[str]:
        return iter(self.words)


if __name__ == "__main__":
    s = Sentence("to be or not")
    print(list(s))
    print(list(s))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

class Sentence:
    """The words of a text; loop over it as often as you like."""

    def __init__(self, text: str) -> None:
        self.words = text.split()
        self.index = 0

    def __iter__(self) -> "Sentence":
        return self

    def __next__(self) -> str:
        if self.index >= len(self.words):
            raise StopIteration
        word = self.words[self.index]
        self.index += 1
        return word


if __name__ == "__main__":
    s = Sentence("to be or not")
    print(list(s))
    print(list(s))

test_main.py

from main import Sentence


def test_words():
    """Ein Sentence liefert seine Wörter in Reihenfolge"""
    got = list(Sentence("to be or not"))
    assert got == ["to", "be", "or", "not"], f"list(Sentence('to be or not')) lieferte {got!r}"


def test_two_loops():
    """Eine zweite Schleife liefert wieder alle Wörter"""
    s = Sentence("to be or not")
    first, second = list(s), list(s)
    assert first == second == ["to", "be", "or", "not"], f"zwei list(s)-Aufrufe lieferten {first!r} und {second!r}"


def test_fresh_iterator():
    """iter(s) liefert einen neuen Iterator, nicht s selbst"""
    s = Sentence("a b")
    assert iter(s) is not s, "iter(s) lieferte s selbst: Ein Sentence sollte einen neuen Iterator herausgeben"


def test_nested_loops():
    """Zwei Schleifen über denselben Sentence können gleichzeitig laufen"""
    s = Sentence("x y")
    got = [a + b for a in s for b in s]
    assert got == ["xx", "xy", "yx", "yy"], f"die verschachtelten Schleifen ergaben {got!r}, erwartet: ['xx', 'xy', 'yx', 'yy']"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 3 von 3

Die ersten n Elemente nehmen

Schreiben Sie take(iterable, n), das eine Liste der ersten n Elemente zurückgibt, oder weniger, wenn das Iterable vorher endet. Es muss mit jedem Iterable funktionieren, auch mit einem Iterator, und darf ihm nicht mehr als n Elemente entnehmen: Nach take(it, 2) liefert next(it) noch das dritte Element. Verwenden Sie iter() und next() und fangen Sie StopIteration ab.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    list(iterable) liest das ganze Iterable und verbraucht einen Iterator vollständig. Nehmen Sie stattdessen ein Element nach dem anderen.

  2. Hinweis 2

    Holen Sie mit it = iter(iterable) einen Iterator und rufen Sie next(it) höchstens n-mal in einer Schleife for _ in range(n): auf.

  3. Hinweis 3

    Setzen Sie result.append(next(it)) in ein try und schreiben Sie break in except StopIteration:.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from collections.abc import Iterable


def take(iterable: Iterable[str], n: int) -> list[str]:
    """Return the first n items of iterable, or fewer if it runs out."""
    it = iter(iterable)
    result: list[str] = []
    for _ in range(n):
        try:
            result.append(next(it))
        except StopIteration:
            break
    return result


if __name__ == "__main__":
    print(take(["a", "b", "c"], 2))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

from collections.abc import Iterable


def take(iterable: Iterable[str], n: int) -> list[str]:
    """Return the first n items of iterable, or fewer if it runs out."""
    return list(iterable)[:n]


if __name__ == "__main__":
    print(take(["a", "b", "c"], 2))

test_main.py

from main import take


def test_first_two():
    """take(['a', 'b', 'c'], 2) ist ['a', 'b']"""
    got = take(["a", "b", "c"], 2)
    assert got == ["a", "b"], f"take(['a', 'b', 'c'], 2) lieferte {got!r}"


def test_runs_out():
    """Wer mehr Elemente verlangt, als es gibt, bekommt alle"""
    got = take("xyz", 5)
    assert got == ["x", "y", "z"], f"take('xyz', 5) lieferte {got!r}, erwartet: ['x', 'y', 'z']"


def test_leaves_the_rest():
    """take(it, 2) lässt das dritte Element im Iterator"""
    it = iter(["a", "b", "c", "d"])
    take(it, 2)
    got = next(it, None)
    assert got == "c", f"nach take(it, 2) lieferte next(it) {got!r}, erwartet: 'c'. take hat zu viele Elemente verbraucht"


def test_zero():
    """take(..., 0) ist eine leere Liste"""
    got = take(["a"], 0)
    assert got == [], f"take(['a'], 0) lieferte {got!r}, erwartet: []"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

next() auf einer Liste aufrufen

numbers = [3, 1, 2]
print(next(numbers))

Was Python ausgibt

TypeError: 'list' object is not an iterator

Warum, und die Lösung

Eine Liste ist iterierbar, aber kein Iterator: Ihr fehlt __next__. Fragen Sie sie zuerst nach einem Iterator, it = iter(numbers), und rufen Sie next(it) auf. Eine for-Schleife erledigt diesen Schritt für Sie.

Eine Iterator-Klasse ohne __iter__

class Countdown:
    def __init__(self, start):
        self.current = start

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1


for n in Countdown(3):
    print(n)

Was Python ausgibt

TypeError: 'Countdown' object is not iterable

Warum, und die Lösung

for ruft zuerst iter() auf dem Objekt auf, und iter() sucht __iter__. Ergänzen Sie def __iter__(self): return self. Mit beiden Methoden erfüllt die Klasse das ganze Protokoll, und list(), sum() und for nehmen sie an.

next() über das Ende hinaus ohne Standardwert

it = iter(["a"])
print(next(it))
print(next(it))

Was Python ausgibt

StopIteration

Warum, und die Lösung

Das zweite next() findet den Iterator verbraucht und löst StopIteration aus, das nur eine Schleife für Sie abfängt. Übergeben Sie einen Standardwert, next(it, None), oder fangen Sie die Ausnahme mit try und except StopIteration ab.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Was eine for-Schleife tut

for item in container: ruft zuerst iter(container) auf, und das liefert einen Iterator. Dann ruft die Schleife immer wieder next() auf diesem Iterator auf, und jeder Aufruf liefert das nächste Element. Gibt es keine Elemente mehr, löst der Iterator StopIteration aus, und die Schleife endet still. Von Hand geht das genauso: it = iter([10, 20]); next(it) liefert 10, dann 20, dann StopIteration. next(it, default) gibt statt der Ausnahme default zurück. Das ist praktisch, wenn Sie nur das erste Element wollen oder einen Ersatzwert brauchen, falls es keins gibt.

Ein eigener Iterator

Ein Iterator ist ein Objekt mit zwei Methoden. __next__ liefert das nächste Element oder löst StopIteration aus, wenn nichts mehr kommt. __iter__ gibt den Iterator selbst zurück: return self. Die zweite Methode wirkt überflüssig, aber durch sie nehmen for und jede Funktion, die ein Iterable erwartet, etwa list, sum und sorted, auch den Iterator an. Seine Position hält der Iterator in Attributen wie self.index, die __next__ liest und weiterschiebt. Hat er einmal StopIteration ausgelöst, sollte er das bei jedem weiteren Aufruf wieder tun.

Iterables und Iteratoren

Ein Iterable ist alles, was iter() annimmt: Listen, Strings, Dicts, Dateien, ranges und Ihre Klassen mit __iter__. Ein Container wie eine Liste gibt jedes Mal einen neuen Iterator heraus; Sie können also zweimal darüber laufen oder zwei Schleifen verschachteln. Ein Iterator reicht für einen Durchlauf: iter() auf ihm liefert dasselbe Objekt, und ist er erschöpft, findet eine zweite Schleife nichts, ganz ohne Fehler. Soll Ihre eigene Klasse mehrfach durchlaufbar sein, geben Sie ihr ein __iter__, das einen neuen Iterator liefert, etwa iter(self.items), statt self.

Quellen

Zuletzt geprüft am 29. September 2026