Przejdź do głównej treści
eLearner.app
Moduł 6 · Lekcja 3 z 423/36 w kursie~10 min
Lekcje modułu (3/4)

Wyrażenia generatorowe

Wyrażenie generujące (generator expression) przypomina wyrażenie listowe, ale używa nawiasów okrągłych zamiast kwadratowych. Różnica w działaniu jest jednak kluczowa: nie buduje ono listy w pamięci, lecz produkuje elementy na żądanie (on-demand), po jednym na raz.

Python
gen = (n * n for n in range(1_000_000))
# natychmiastowe działanie, stała pamięć: nie wykonano jeszcze żadnych obliczeń

list_comp = [n * n for n in range(1_000_000)]
# alokuje w pamięci 1 milion liczb całkowitych

Leniwa ocena (lazy evaluation)

Generator nie wykonuje żadnej pracy, dopóki ktoś nie zażąda kolejnej wartości:

Python
gen = (n * n for n in range(5))
next(gen)    # 0    (obliczone w tym momencie)
next(gen)    # 1
next(gen)    # 4
# ... i tak dalej, aż do zgłoszenia StopIteration

Generator ulega wyczerpaniu: po skonsumowaniu wszystkich elementów pozostaje pusty na zawsze. Aby użyć go ponownie, musisz go utworzyć na nowo.

Kiedy używać

Gdy przekazujesz wyniki do funkcji, która konsumuje je sekwencyjnie:

Python
total = sum(n * n for n in range(1_000_000))
# brak listy pośredniej, stałe zużycie pamięci

Funkcje takie jak sum, any, all, max, min, "".join(...) przyjmują generatory. Jeśli funkcja zewnętrzna przyjmuje tylko jeden argument, możesz pominąć nawiasy samego wyrażenia generującego:

Python
sum((n * n for n in range(10)))    # OK
sum(n * n for n in range(10))      # IDENTYCZNE działanie, bardziej czytelne

any i all: krótkie spięcie (short-circuit)

Funkcje any(...) i all(...) przerywają iterację, gdy tylko znają ostateczny wynik. W przypadku generatora kolejne elementy nie są wtedy nawet obliczane.

Python
nums = [1, 2, 3, 4, 5]
any(n > 3 for n in nums)    # True (zatrzymuje się na 4)
all(n > 0 for n in nums)    # True
all(n > 2 for n in nums)    # False (zatrzymuje się na 1)

Wzorzec: leniwy rurociąg (lazy pipeline)

Python
righe = (linea.strip() for linea in testo.splitlines())
non_vuote = (r for r in righe if r)
prime_dieci = []
for r in non_vuote:
    prime_dieci.append(r)
    if len(prime_dieci) == 10:
        break

Brak list pośrednich, nawet przy olbrzymich plikach wejściowych.

Konsumowanie generatorów za pomocą sum, any, all

Wyrażenia generujące doskonale nadają się do bezpośredniego przekazywania do funkcji konsumujących obiekty iterowalne, takich jak sum(), any() czy all(). W takim przypadku możesz pominąć zewnętrzne nawiasy wyrażenia generującego, pisząc po prostu:

Python
squares_sum = sum(x**2 for x in range(10))

Pozwala to zachować czysty kod i wysoką wydajność pamięciową.

Spróbuj sam

Ćwiczenie#python.m6.l3.e1
Próby: 0Ładowanie...

Oblicz sumę kwadratów liczb od 1 do 100 włącznie przy użyciu wyrażenia generującego wewnątrz funkcji sum(). Przypisz wynik do zmiennej `tot`. Oceń `tot`.

Ładowanie edytora...
Pokaż wskazówkę

Przedział range(1, 101) obejmuje liczbę 100.

Rozwiązanie dostępne po 3 próbach

Ćwiczenie powtórzeniowe

Ćwiczenie#python.m6.l3.e2
Próby: 0Ładowanie...

Mając daną listę `words = ['ciao', 'mondo', 'PYTHON', 'java']`, użyj funkcji any() z wyrażeniem generującym do sprawdzenia, czy CO NAJMNIEJ jedno słowo składa się wyłącznie z wielkich liter (.isupper()). Przypisz wynik do zmiennej `has_uppercase`. Oceń `has_uppercase`.

Ładowanie edytora...
Pokaż wskazówkę

any(p.isupper() for p in words)

Rozwiązanie dostępne po 3 próbach

Dodatkowe wyzwanie

Ćwiczenie#python.m6.l3.e3
Próby: 0Ładowanie...

Użyj wyrażenia generującego przekazanego bezpośrednio do funkcji `sum()` do obliczenia sumy kwadratów wszystkich liczb całkowitych od 1 do 100 włącznie. Zapisz wynik w zmiennej `total_sum` i go oceń.

Ładowanie edytora...
Pokaż wskazówkę

Napisz total_sum = sum(x**2 for x in range(1, 101)). Podwójne nawiasy nie są potrzebne.

Rozwiązanie dostępne po 3 próbach