Aufwärmen · Aufgabe 1 von 7
// 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.
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
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)Ü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):Ü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")Üben · Aufgabe 5 von 7
Eine Klasse definiert nur __len__ und __getitem__. Ordnen Sie jeder Operation zu, was Python aufruft.
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)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.pyAusgabe
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
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.
Hinweis 2
In __contains__ liefern Sie False, wenn nicht isinstance(title, str), und vergleichen sonst kleingeschriebene Fassungen.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.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
Hinweis 1
Ändern Sie die Klassenzeile zu class TagSet(Set[str]):.
Hinweis 2
Die Mixins bauen neue Sets mit TagSet(iterable), und das vorhandene __init__ nimmt das schon an.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHä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 >= 0Warum, 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: 0Warum, 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.