Zum Inhalt springen
aviral gupta

// I3.3 · ca. 26 Min. · Aufbau

Generator-Ausdrücke und verzögerte Auswertung

Nach dieser Lektion geben Sie Generator-Ausdrücke direkt an sum, max, join, any und all, sagen vorher, wo any und all aufhören, und verwenden keinen verbrauchten Generator erneut.

Lektion 3 von 6 in I3 Iteration und funktionale Werkzeuge

Danach können Sie

  • Generator-Ausdrücke schreiben und direkt an sum, min, max und join übergeben
  • any() und all() mit einem Generator-Ausdruck verwenden und vorhersagen, wo sie aufhören
  • Vorhersagen, was passiert, wenn ein Generator-Ausdruck zweimal verwendet oder verzögert ausgewertet wird
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Modul B4: eine List Comprehension mit Bedingung. Was gibt dieser Code aus?

    print([n * 2 for n in range(4) if n % 2 == 0])
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Dieselben Quadrate werden zweimal summiert. Was gibt der Code aus?

    squares = (n * n for n in range(4))
    print(sum(squares), sum(squares))
  3. Üben · Aufgabe 3 von 7

    Setzen Sie das Schlüsselwort ein, das den Generator-Ausdruck vervollständigt, damit das Programm die Gesamtzahl der Buchstaben ausgibt.

    words = ["hi", "there"]
    print(sum(len(w) ____ w in words))
    sum(len(w) w in words)
  4. Üben · Aufgabe 4 von 7

    Was gibt dieser Code aus?

    nums = [3, 8, 12]
    print(any(n > 10 for n in nums), all(n > 2 for n in nums))
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jedem Ausdruck seinen Wert zu.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. check() gibt jede Zahl aus, die es prüft. Was gibt das Programm aus?

    def check(n):
        print("checking", n, end="; ")
        return n > 1
    
    print(any(check(n) for n in [0, 2, 5]))
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. orders = [("pen", 1.5, 4), ("book", 12.0, 2), ("lamp", 35.0, 1)] enthält Name, Preis und Menge. Geben Sie mit je einem Generator-Ausdruck den Gesamtpreis als total: 65.00 aus, ob jede Menge über 0 liegt und ob irgendein Preis über 30 liegt. Bauen Sie keine Listen.

    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

Statistik aus dem Log eines Webservers

Jede Log-Zeile enthält eine Methode, einen Pfad, einen Statuscode und die gesendeten Bytes. Jede Kennzahl ist ein Generator-Ausdruck, der direkt an sum, any, all, max oder join geht, es entsteht also nie eine Liste von Zahlen. Das Programm summiert außerdem denselben Generator zweimal, um zu zeigen, dass der zweite Durchlauf nichts findet.

main.py

log = [
    "GET /index.html 200 512",
    "GET /missing 404 0",
    "POST /login 200 128",
    "GET /about.html 200 2048",
    "GET /admin 403 0",
]

sizes = (int(line.split()[3]) for line in log)
print("bytes sent:", sum(sizes))
print("bytes again:", sum(sizes))

print("any errors:", any(int(line.split()[2]) >= 400 for line in log))
print("all GET:", all(line.startswith("GET") for line in log))
print("largest:", max(int(line.split()[3]) for line in log))
empty = ", ".join(line.split()[1] for line in log if line.endswith(" 0"))
print("empty replies:", empty)

Ausführen mit

python main.py

Ausgabe

bytes sent: 2688
bytes again: 0
any errors: True
all GET: False
largest: 2048
empty replies: /missing, /admin
  • sizes wurde vom ersten sum verbraucht, also addierte das zweite sum nichts und lieferte 0.
  • any hörte bei der zweiten Zeile auf, der ersten mit einem Status von 400 oder mehr.
  • all hörte bei der POST-Zeile auf: Ein falsches Element genügt für False.
  • Die if-Klausel wirkt wie in einer List Comprehension: join bekam nur die Pfade der Zeilen, die auf " 0" enden.
Ä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

Wortstatistik in je einer Zeile

Vervollständigen Sie drei Funktionen mit je einem Generator-Ausdruck. total_length(words) liefert die Zahl der Buchstaben aller Wörter zusammen. has_long_word(words, limit) liefert True, wenn ein Wort länger als limit ist. all_lowercase(words) liefert True, wenn jedes Wort ganz kleingeschrieben ist, was str.islower() Ihnen sagt; für eine leere Liste ist es True.

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

    Jeder Rumpf ist ein einziges return mit sum, any oder all um einen Generator-Ausdruck.

  2. Hinweis 2

    sum(len(w) for w in words) addiert die Längen; any(len(w) > limit for w in words) prüft auf ein langes Wort.

  3. Hinweis 3

    all(w.islower() for w in words) ist für eine leere Liste schon True, ein zusätzliches if ist also nicht nötig.

Eine Lösung zeigen

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

def total_length(words: list[str]) -> int:
    """Return the number of letters in all words together."""
    return sum(len(w) for w in words)


def has_long_word(words: list[str], limit: int) -> bool:
    """Return True if any word is longer than limit."""
    return any(len(w) > limit for w in words)


def all_lowercase(words: list[str]) -> bool:
    """Return True if every word is all lowercase."""
    return all(w.islower() for w in words)


if __name__ == "__main__":
    words = ["tea", "Coffee", "water"]
    print(total_length(words), has_long_word(words, 5), all_lowercase(words))
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

def total_length(words: list[str]) -> int:
    """Return the number of letters in all words together."""
    return 0


def has_long_word(words: list[str], limit: int) -> bool:
    """Return True if any word is longer than limit."""
    return False


def all_lowercase(words: list[str]) -> bool:
    """Return True if every word is all lowercase."""
    return False


if __name__ == "__main__":
    words = ["tea", "Coffee", "water"]
    print(total_length(words), has_long_word(words, 5), all_lowercase(words))

test_main.py

from main import all_lowercase, has_long_word, total_length


def test_total_length():
    """tea, Coffee und water haben zusammen 14 Buchstaben"""
    got = total_length(["tea", "Coffee", "water"])
    assert got == 14, f"total_length(['tea', 'Coffee', 'water']) lieferte {got!r}, erwartet: 14"


def test_has_long_word():
    """Coffee ist länger als 5 Buchstaben, aber kein Wort ist länger als 6"""
    words = ["tea", "Coffee", "water"]
    got = has_long_word(words, 5), has_long_word(words, 6)
    assert got == (True, False), f"has_long_word mit den Grenzen 5 und 6 lieferte {got!r}, erwartet: (True, False)"


def test_all_lowercase():
    """Coffee ist nicht kleingeschrieben, tea und water schon"""
    got = all_lowercase(["tea", "Coffee"]), all_lowercase(["tea", "water"])
    assert got == (False, True), f"all_lowercase lieferte {got!r}, erwartet: (False, True)"


def test_empty_list():
    """Ohne Wörter: 0 Buchstaben, kein langes Wort, alles kleingeschrieben"""
    got = total_length([]), has_long_word([], 0), all_lowercase([])
    assert got == (0, False, True), f"für [] lieferten die Funktionen {got!r}, erwartet: (0, False, 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

Übung 2 von 2

Eine Zusammenfassung, die einen Generator übersteht

summary(numbers) liefert den kleinsten Wert, den größten Wert und den Mittelwert. Der Starter funktioniert mit einer Liste, scheitert aber, wenn numbers ein Generator ist, weil min ihn verbraucht. Sorgen Sie dafür, dass es mit jedem Iterable funktioniert, mit einem einzigen Durchlauf über numbers. Ganz ohne Zahlen löst es ValueError aus.

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

    Ein Generator lässt sich nur einmal lesen, eine Liste beliebig oft.

  2. Hinweis 2

    Beginnen Sie mit values = list(numbers). Das ist der eine Durchlauf; danach verwenden Sie überall values.

  3. Hinweis 3

    if not values: raise ValueError("no numbers"). Dann return min(values), max(values), sum(values) / len(values).

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 summary(numbers: Iterable[float]) -> tuple[float, float, float]:
    """Return the smallest, the largest and the mean of numbers."""
    values = list(numbers)
    if not values:
        raise ValueError("no numbers")
    return min(values), max(values), sum(values) / len(values)


if __name__ == "__main__":
    print(summary(n * 1.5 for n in [2, 4, 6]))
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 summary(numbers: Iterable[float]) -> tuple[float, float, float]:
    """Return the smallest, the largest and the mean of numbers."""
    smallest = min(numbers)
    largest = max(numbers)
    mean = sum(numbers) / sum(1 for _ in numbers)
    return smallest, largest, mean


if __name__ == "__main__":
    print(summary(n * 1.5 for n in [2, 4, 6]))

test_main.py

from main import summary


def test_list():
    """Eine Liste [3, 1, 2] ergibt (1, 3, 2.0)"""
    got = summary([3, 1, 2])
    assert got == (1, 3, 2.0), f"summary([3, 1, 2]) lieferte {got!r}, erwartet: (1, 3, 2.0)"


def test_generator():
    """Auch ein Generator-Ausdruck funktioniert"""
    try:
        got = summary(n * 1.5 for n in [2, 4, 6])
    except ValueError as error:
        raise AssertionError(f"summary(<generator>) löste ValueError aus: {error}. Ein früherer Durchlauf hat den Generator verbraucht") from None
    assert got == (3.0, 9.0, 6.0), f"summary(n * 1.5 for n in [2, 4, 6]) lieferte {got!r}, erwartet: (3.0, 9.0, 6.0)"


def test_single_value():
    """Ein einzelner Wert ist Minimum, Maximum und Mittelwert"""
    got = summary(iter([7]))
    assert got == (7, 7, 7.0), f"summary(iter([7])) lieferte {got!r}, erwartet: (7, 7, 7.0)"


def test_empty():
    """Ohne Zahlen kommt ValueError"""
    try:
        got = summary([])
    except ValueError:
        return
    raise AssertionError(f"summary([]) lieferte {got!r}, statt ValueError auszulösen")

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

Einen Generator-Ausdruck zweimal verwenden

prices = [4.5, 12.0, 3.25]
doubled = (p * 2 for p in prices)
total = sum(doubled)
biggest = max(doubled)
print(total, biggest)

Was Python ausgibt

ValueError: max() iterable argument is empty

Warum, und die Lösung

sum hat den Generator verbraucht, also bekam max einen leeren Iterator. Brauchen Sie die Werte mehr als einmal, bauen Sie mit eckigen Klammern eine Liste: doubled = [p * 2 for p in prices]. Oder schreiben Sie für jede Verwendung einen neuen Generator-Ausdruck.

Ein Generator-Ausdruck neben einem weiteren Argument

nums = [1, 2, 3]
print(sum(n * n for n in nums, 10))

Was Python ausgibt

SyntaxError: Generator expression must be parenthesized

Warum, und die Lösung

Die Klammern eines Generator-Ausdrucks dürfen nur fehlen, wenn er das einzige Argument des Aufrufs ist. Mit einem zweiten Argument braucht er eigene: sum((n * n for n in nums), 10). Hier beginnt sum bei 10 zu zählen und liefert 24.

Zahlen mit join verbinden

ids = [7, 12, 30]
print(", ".join(n for n in ids))

Was Python ausgibt

TypeError: sequence item 0: expected str instance, int found

Warum, und die Lösung

join verbindet nur Strings, und der Generator liefert ihm ints. Wandeln Sie im Ausdruck um: ", ".join(str(n) for n in ids) gibt 7, 12, 30 aus.

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

Eine Comprehension in runden Klammern

(x * x for x in range(10)) sieht aus wie eine List Comprehension, ergibt in runden Klammern aber einen Generator: einen Iterator, der jeden Wert erst auf Anfrage erzeugt. Beim Anlegen des Ausdrucks wird nichts berechnet. Ist er das einzige Argument eines Aufrufs, entfallen seine eigenen Klammern: sum(x * x for x in nums). Verwenden Sie ihn, wenn die Werte einmal und sofort verbraucht werden, von sum, min, max, "".join, set oder einer for-Schleife. Er hält nie alle Werte im Speicher und passt darum zu großen Eingaben.

any() und all() hören früh auf

any(iterable) liefert True, sobald ein Element wahr ist; all(iterable) liefert False, sobald ein Element falsch ist. Mit einem Generator-Ausdruck hören sie genau dort auf, ihn zu verbrauchen: any(n > 100 for n in nums) sieht sich den Rest von nums nach der ersten großen Zahl nicht mehr an. Für ein leeres Iterable liefert any False und all True. all(...) liest sich also als „jedes Element besteht“, any(...) als „mindestens eines besteht“.

Ein Durchlauf, und später als gedacht

Ein Generator-Ausdruck ist ein Iterator und nach einem Durchlauf verbraucht. total = sum(g) und danach max(g) scheitert: max bekommt einen leeren Iterator und löst ValueError aus. Speichern Sie die Werte in einer Liste, wenn Sie sie zweimal brauchen. Die Verzögerung verschiebt auch Fehler: Bei (10 / d for x in nums) tritt ein ZeroDivisionError erst auf, wenn ein Wert verlangt wird, nicht dort, wo der Ausdruck steht. Nur das Iterable des ersten for, hier nums, wird sofort ausgewertet.

Quellen

Zuletzt geprüft am 29. September 2026