Zum Inhalt springen
aviral gupta

// A1.3 · ca. 30 Min. · Vertiefung

Container-Protokolle

Nach dieser Lektion bauen Sie eine Klasse, die mit len(), Indizes, Slices, in und for-Schleifen funktioniert, und holen sich den Rest einer Sequenz- oder Set-API aus collections.abc.

Lektion 3 von 6 in A1 Das Datenmodell

Danach können Sie

  • __len__, __getitem__, __contains__ und __iter__ schreiben, damit len(), [], in und for funktionieren
  • Die Rückfälle vorhersagen: in und for über __getitem__, Wahrheit über __len__
  • Mit Basisklassen aus collections.abc Mixin-Methoden aus wenigen abstrakten erhalten
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Modul I3: Eine for-Schleife ruft zuerst iter() für das Objekt auf. Nach welcher Methode sucht iter() zuerst?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Letters definiert nur __getitem__. Was gibt das aus?

    class Letters:
        def __getitem__(self, index):
            return "abc"[index]
    
    
    d = Letters()
    print(list(d), "b" in d)
  3. Üben · Aufgabe 3 von 7

    Setzen Sie den Methodennamen ein, damit len(stack) funktioniert und ein leerer Stack als falsch gilt.

    class Stack:
        def __init__(self, items):
            self._items = list(items)
    
        def ____(self):
            return len(self._items)
    def (self):
  4. Üben · Aufgabe 4 von 7

    Queue definiert __len__, aber kein __bool__. Was gibt das aus?

    class Queue:
        def __init__(self):
            self.jobs = []
    
        def __len__(self):
            return len(self.jobs)
    
    
    q = Queue()
    print(bool(q), "empty" if not q else "busy")
  5. Üben · Aufgabe 5 von 7

    Eine Klasse definiert nur __len__ und __getitem__. Ordnen Sie jeder Operation zu, was Python aufruft.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Countdown ist sein eigener Iterator. Was gibt das aus?

    class Countdown:
        def __init__(self, start):
            self.current = start
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.current <= 0:
                raise StopIteration
            self.current -= 1
            return self.current + 1
    
    
    c = Countdown(3)
    print(2 in c, 2 in c)
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie eine Klasse Timeline, die von collections.abc.Sequence erbt und eine Liste von Ereignissen hält. Schreiben Sie nur __len__ und __getitem__ und zeigen Sie dann, dass in, count(), index() und reversed() ebenfalls funktionieren.

    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

Ein Kartenstapel als Sequence

Deck erbt von collections.abc.Sequence und schreibt zwei Methoden: __len__ und __getitem__. Ein Slice liefert einen neuen Deck, weil __getitem__ auf ein slice-Objekt prüft. Alles andere in der Ausgabe, also in, index(), reversed() und die for-Schleife in der Comprehension, stammt aus den Mixin-Methoden von Sequence.

main.py

from collections.abc import Sequence


class Deck(Sequence):
    """Cards in order. Two methods written, the rest inherited from Sequence."""

    def __init__(self, cards: list[str]) -> None:
        self._cards = list(cards)

    def __len__(self) -> int:
        return len(self._cards)

    def __getitem__(self, index):
        if isinstance(index, slice):
            return Deck(self._cards[index])
        return self._cards[index]

    def __repr__(self) -> str:
        return f"Deck({self._cards!r})"


deck = Deck(["A", "K", "Q", "J", "10"])
print(len(deck), deck[0], deck[-1])
print("Top three:", deck[:3])
print("Q" in deck, "2" in deck)
print("Position of J:", deck.index("J"))
print("Reversed:", list(reversed(deck)))
print("Face cards:", [card for card in deck if card in ("K", "Q", "J")])

Ausführen mit

python main.py

Ausgabe

5 A 10
Top three: Deck(['A', 'K', 'Q'])
True False
Position of J: 3
Reversed: ['10', 'J', 'Q', 'K', 'A']
Face cards: ['K', 'Q', 'J']
  • deck[-1] funktioniert, weil die innere Liste negative Indizes versteht.
  • deck[:3] übergibt slice(None, 3, None) an __getitem__, das das Ergebnis in einen neuen Deck packt.
  • in, index() und reversed() sind Sequence-Mixins auf Basis von __len__ und __getitem__.
  • Die Comprehension iteriert mit dem geerbten __iter__, das beim ersten IndexError anhält.
Ä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 2

Ein Playlist-Container

Geben Sie Playlist in main.py vier Methoden: __len__ (Anzahl der Songs), __getitem__ (Titel an einer Position; IndexError hinter dem Ende), __contains__ (Titelprüfung ohne Rücksicht auf Groß- und Kleinschreibung, False für Nicht-Strings) und __iter__ (die Titel der Reihe nach).

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

    Geben Sie an die Liste weiter: len(self._songs), self._songs[index] und iter(self._songs). Die Liste löst IndexError schon selbst aus.

  2. Hinweis 2

    In __contains__ liefern Sie False, wenn nicht isinstance(title, str), und vergleichen sonst kleingeschriebene Fassungen.

  3. Hinweis 3

    return title.lower() in [song.lower() for song in self._songs]

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 Playlist:
    """Song titles in play order."""

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

    def __len__(self) -> int:
        return len(self._songs)

    def __getitem__(self, index: int) -> str:
        return self._songs[index]

    def __contains__(self, title: object) -> bool:
        if not isinstance(title, str):
            return False
        return title.lower() in [song.lower() for song in self._songs]

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


if __name__ == "__main__":
    mix = Playlist(["Blue Monday", "Heroes", "Vienna"])
    print(len(mix), mix[0], "heroes" in mix, list(mix))
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 Iterator


class Playlist:
    """Song titles in play order."""

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

    # Add __len__, __getitem__ (by position), __contains__ (ignores case)
    # and __iter__ (yields the titles in order).


if __name__ == "__main__":
    mix = Playlist(["Blue Monday", "Heroes", "Vienna"])
    print(len(mix), mix[0], "heroes" in mix, list(mix))

test_main.py

from main import Playlist


def test_len_and_truth():
    """len() zählt die Songs; eine leere Playlist ist falsch"""
    got = len(Playlist(["a", "b", "c"])), bool(Playlist([]))
    assert got == (3, False), f"len() und bool() ergaben {got!r}, erwartet: (3, False)"


def test_index():
    """p[0] und p[-1] liefern den ersten und letzten Song"""
    p = Playlist(["Blue Monday", "Heroes", "Vienna"])
    got = p[0], p[-1]
    assert got == ("Blue Monday", "Vienna"), f"p[0], p[-1] ergaben {got!r}"


def test_index_out_of_range():
    """Ein Index hinter dem Ende löst IndexError aus"""
    try:
        Playlist(["a"])[5]
    except IndexError:
        return
    raise AssertionError("Playlist(['a'])[5] hat keinen IndexError ausgelöst")


def test_contains_ignores_case():
    """in ignoriert Groß- und Kleinschreibung"""
    p = Playlist(["Blue Monday", "Heroes"])
    got = "heroes" in p, "HEROES" in p, "Vienna" in p, 42 in p
    assert got == (True, True, False, False), f"die in-Tests ergaben {got!r}, erwartet: (True, True, False, False)"


def test_iteration():
    """Eine for-Schleife liefert die Titel der Reihe nach"""
    got = list(Playlist(["x", "y"]))
    assert got == ["x", "y"], f"list(Playlist(['x', 'y'])) ergab {got!r}"

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 2

Ein Set aus drei Methoden

Machen Sie TagSet in main.py zu einer Unterklasse von collections.abc.Set[str] und schreiben Sie ihre drei abstrakten Methoden __contains__, __iter__ und __len__ mit self._tags. Die Set-Mixins liefern dann &, |, -, <= und == kostenlos.

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

    Ändern Sie die Klassenzeile zu class TagSet(Set[str]):.

  2. Hinweis 2

    Die Mixins bauen neue Sets mit TagSet(iterable), und das vorhandene __init__ nimmt das schon an.

  3. Hinweis 3

    __contains__ liefert tag in self._tags, __iter__ liefert iter(self._tags) und __len__ liefert len(self._tags).

Eine Lösung zeigen

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

from collections.abc import Iterable, Iterator, Set


class TagSet(Set[str]):
    """Tags without duplicates, kept in the order first seen."""

    def __init__(self, tags: Iterable[str]) -> None:
        self._tags = list(dict.fromkeys(tags))

    def __repr__(self) -> str:
        return f"TagSet({self._tags!r})"

    def __contains__(self, tag: object) -> bool:
        return tag in self._tags

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

    def __len__(self) -> int:
        return len(self._tags)


if __name__ == "__main__":
    python = TagSet(["code", "python", "code", "web"])
    print(python)
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, Iterator, Set


class TagSet:
    """Tags without duplicates, kept in the order first seen."""

    def __init__(self, tags: Iterable[str]) -> None:
        self._tags = list(dict.fromkeys(tags))

    def __repr__(self) -> str:
        return f"TagSet({self._tags!r})"

    # Make TagSet a subclass of Set[str] and write the three abstract
    # methods: __contains__, __iter__ and __len__.


if __name__ == "__main__":
    python = TagSet(["code", "python", "code", "web"])
    print(python)

test_main.py

from collections.abc import Set

from main import TagSet


def test_is_a_set():
    """TagSet ist ein collections.abc.Set"""
    assert isinstance(TagSet([]), Set), "TagSet erbt nicht von collections.abc.Set"


def test_no_duplicates():
    """Doppelte fallen weg, die erste Reihenfolge bleibt"""
    got = list(TagSet(["b", "a", "b"])), len(TagSet(["b", "a", "b"]))
    assert got == (["b", "a"], 2), f"list() und len() ergaben {got!r}, erwartet: (['b', 'a'], 2)"


def test_operators_from_mixins():
    """&, | und - kommen von den Set-Mixins"""
    a, b = TagSet(["x", "y"]), TagSet(["y", "z"])
    got = sorted(a & b), sorted(a | b), sorted(a - b)
    assert got == (["y"], ["x", "y", "z"], ["x"]), f"&, | und - ergaben {got!r}"


def test_comparisons_from_mixins():
    """<= prüft auf Teilmenge, == vergleicht den Inhalt"""
    got = TagSet(["x"]) <= TagSet(["x", "y"]), TagSet(["a", "b"]) == TagSet(["b", "a"])
    assert got == (True, True), f"<= und == ergaben {got!r}, erwartet: (True, True)"

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

__len__ mit negativem Ergebnis

class Stack:
    def __init__(self):
        self.items = []

    def __len__(self):
        return -1


print(len(Stack()))

Was Python ausgibt

ValueError: __len__() should return >= 0

Warum, und die Lösung

len() prüft das Ergebnis: Es muss ein int von 0 oder mehr sein. Liefern Sie die echte Anzahl, meist len() der eingepackten Sammlung. Für „Größe unbekannt“ sollte eine Klasse gar kein __len__ definieren.

Eine abstrakte Methode vergessen

from collections.abc import Sequence


class Deck(Sequence):
    def __init__(self, cards):
        self.cards = list(cards)

    def __getitem__(self, index):
        return self.cards[index]


deck = Deck(["A", "K", "Q"])

Was Python ausgibt

TypeError: Can't instantiate abstract class Deck without an implementation for abstract method '__len__'

Warum, und die Lösung

Sequence hat zwei abstrakte Methoden, __getitem__ und __len__, und ein Objekt lässt sich erst erzeugen, wenn beide existieren. Die Meldung nennt die fehlende. Ergänzen Sie def __len__(self): return len(self.cards).

Ein __getitem__ im Mapping-Stil in einer for-Schleife

class Scores:
    def __init__(self, by_name):
        self.by_name = by_name

    def __getitem__(self, name):
        return self.by_name[name]


scores = Scores({"ada": 90, "bob": 72})
for score in scores:
    print(score)

Was Python ausgibt

KeyError: 0

Warum, und die Lösung

Ohne __iter__ weicht for auf __getitem__(0), (1), … aus, und nur ein IndexError beendet diese Schleife. Ein Mapping löst KeyError aus, also stürzt die Schleife bei 0 ab. Geben Sie der Klasse ein __iter__, etwa return iter(self.by_name).

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

Vier Methoden, vier Operationen

len(x) ruft __len__ auf, das ein int von 0 oder mehr liefern muss. x[i] ruft __getitem__(i) auf; x[1:3] übergibt ein slice-Objekt, prüfen Sie also isinstance(index, slice), wenn Sie Slices unterstützen. v in x ruft __contains__ auf. for v in x ruft iter(x) auf, das __iter__ aufruft und jedes Mal einen frischen Iterator liefern muss. Bei einem falschen Index löst __getitem__ IndexError (Sequenzen) oder KeyError (Mappings) aus, bei einem Index falschen Typs TypeError.

Die Rückfälle

Python füllt Lücken. Ohne __contains__ iteriert in über __iter__. Ohne __iter__ nutzen for und in das alte Sequenzprotokoll: __getitem__(0), (1), (2) und so weiter, bis IndexError kommt. Eine Klasse mit nur __getitem__ ist also schon iterierbar, und ein KeyError dort lässt eine for-Schleife abstürzen. Ohne __bool__ entscheidet __len__ über die Wahrheit: Länge 0 ist falsch. Ohne __reversed__ nutzt reversed() __len__ und __getitem__.

collections.abc erledigt den Rest

Erben Sie von einer abstrakten Basisklasse, schreiben Sie deren abstrakte Methoden und erben Sie die Mixin-Methoden. Sequence braucht __getitem__ und __len__ und liefert __contains__, __iter__, __reversed__, index und count. Set braucht __contains__, __iter__ und __len__ und liefert <=, ==, &, | und -. Fehlt eine abstrakte Methode, löst das Erzeugen eines Objekts TypeError aus. isinstance(x, Sized) klappt auch bei Klassen, die nur __len__ definieren.

Quellen

Zuletzt geprüft am 29. September 2026