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.
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łkowitychLeniwa ocena (lazy evaluation)
Generator nie wykonuje żadnej pracy, dopóki ktoś nie zażąda kolejnej wartości:
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 StopIterationGenerator 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:
total = sum(n * n for n in range(1_000_000))
# brak listy pośredniej, stałe zużycie pamięciFunkcje 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:
sum((n * n for n in range(10))) # OK
sum(n * n for n in range(10)) # IDENTYCZNE działanie, bardziej czytelneany 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.
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)
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:
breakBrak 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:
squares_sum = sum(x**2 for x in range(10))Pozwala to zachować czysty kod i wysoką wydajność pamięciową.
Spróbuj sam
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`.
Pokaż wskazówkę
Przedział range(1, 101) obejmuje liczbę 100.
Rozwiązanie dostępne po 3 próbach
Ćwiczenie powtórzeniowe
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`.
Pokaż wskazówkę
any(p.isupper() for p in words)
Rozwiązanie dostępne po 3 próbach
Dodatkowe wyzwanie
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ń.
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