Generator to specjalna funkcja która zwraca iterator. Zamiast zwracać wszystkie wartości na raz, generuje je po kolei, „leniwie” (lazy evaluation).
Dlaczego generatory?
- Oszczędność pamięci (nie trzymają wszystkich wartości)
- Efektywność dla dużych zbiorów danych
- Nieskończone sekwencje
- Pipeline’y przetwarzania danych
Kluczowe słowo: yield (zamiast return)
Zwykła funkcja vs Generator
Zwykła funkcja:
def liczby_zwykla():
"""Zwraca listę - wszystko naraz w pamięci"""
wynik = []
for i in range(5):
wynik.append(i)
return wynik
print(liczby_zwykla()) # [0, 1, 2, 3, 4]
Generator:
def liczby_generator():
"""Generator - zwraca po kolei"""
for i in range(5):
yield i # yield zamiast append/return
# Generator nie wykonuje się od razu
gen = liczby_generator()
print(gen) # <generator object liczby_generator at 0x...>
# Pobierz wartości po kolei
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
# Lub w pętli
for liczba in liczby_generator():
print(liczba)
# 0, 1, 2, 3, 4
Jak działa yield
def prosty_generator():
print("Start")
yield 1
print("Między 1 a 2")
yield 2
print("Między 2 a 3")
yield 3
print("Koniec")
gen = prosty_generator()
print("Pierwsza wartość:")
print(next(gen))
# Start
# 1
print("Druga wartość:")
print(next(gen))
# Między 1 a 2
# 2
print("Trzecia wartość:")
print(next(gen))
# Między 2 a 3
# 3
print("Próba czwartej:")
# next(gen) # StopIteration - generator się wyczerpał
Kluczowe:
- Generator „zatrzymuje się” po każdym
yield - Przy następnym
next()kontynuuje od miejsca zatrzymania - Zapamiętuje stan (zmienne lokalne)
Pierwszy praktyczny generator
def liczby_parzyste(n):
"""Generuje liczby parzyste od 0 do n"""
for i in range(n + 1):
if i % 2 == 0:
yield i
# Użycie
for liczba in liczby_parzyste(10):
print(liczba)
# 0, 2, 4, 6, 8, 10
Generator vs Lista – porównanie pamięci
import sys
# Lista - wszystko w pamięci
lista = [x for x in range(1000000)]
print(f"Lista zajmuje: {sys.getsizeof(lista)} bajtów")
# Lista zajmuje: 8000056 bajtów
# Generator - tylko instrukcje
generator = (x for x in range(1000000))
print(f"Generator zajmuje: {sys.getsizeof(generator)} bajtów")
# Generator zajmuje: 104 bajty
# Generator jest ~77000 razy mniejszy!
Generator expressions (wyrażenia generatorowe)
Podobne do list comprehensions, ale z () zamiast []:
# List comprehension - lista w pamięci
lista = [x**2 for x in range(10)]
print(lista) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Generator expression - generator
generator = (x**2 for x in range(10))
print(generator) # <generator object ...>
# Użycie
for wartosc in generator:
print(wartosc)
Nieskończone generatory
def liczby_naturalne():
"""Nieskończony generator liczb naturalnych"""
n = 0
while True: # Nieskończona pętla!
yield n
n += 1
# Użycie - pobierz pierwsze 10
gen = liczby_naturalne()
for i in range(10):
print(next(gen))
# 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Generator Fibonacciego
def fibonacci():
"""Nieskończony generator liczb Fibonacciego"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Pierwsze 10 liczb
gen = fibonacci()
for i in range(10):
print(next(gen))
# 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
Generator z limitem
def fibonacci_limit(limit):
"""Generator Fibonacciego do limitu"""
a, b = 0, 1
while a <= limit:
yield a
a, b = b, a + b
# Wszystkie liczby Fibonacciego <= 100
for liczba in fibonacci_limit(100):
print(liczba)
# 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Generator odczytujący plik
def czytaj_linie(nazwa_pliku):
"""Generator czytający plik linia po linii"""
with open(nazwa_pliku, 'r', encoding='utf-8') as plik:
for linia in plik:
yield linia.strip()
# Użycie - oszczędność pamięci dla dużych plików
for linia in czytaj_linie('duzy_plik.txt'):
print(linia)
# Przetwarza jedną linię na raz, nie cały plik!
Generator z filtrowaniem
def wiersze_z_slowem(nazwa_pliku, slowo):
"""Generator zwracający wiersze zawierające słowo"""
with open(nazwa_pliku, 'r', encoding='utf-8') as plik:
for linia in plik:
if slowo in linia:
yield linia.strip()
# Znajdź wszystkie linie z "Python"
for linia in wiersze_z_slowem('log.txt', 'Python'):
print(linia)
Generator liczb pierwszych
def liczby_pierwsze(limit):
"""Generator liczb pierwszych do limitu"""
def czy_pierwsza(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
for n in range(2, limit + 1):
if czy_pierwsza(n):
yield n
# Wszystkie liczby pierwsze <= 50
for liczba in liczby_pierwsze(50):
print(liczba)
# 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47
Generator z yield from
yield from deleguje do innego generatora:
def generator1():
yield 1
yield 2
yield 3
def generator2():
yield 4
yield 5
yield 6
def polaczony():
yield from generator1()
yield from generator2()
for wartosc in polaczony():
print(wartosc)
# 1, 2, 3, 4, 5, 6
Generator zagnieżdżonych list
def splaszcz(lista):
"""Spłaszcza zagnieżdżoną listę"""
for element in lista:
if isinstance(element, list):
yield from splaszcz(element) # Rekurencja!
else:
yield element
zagniezdzona = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]
for element in splaszcz(zagniezdzona):
print(element)
# 1, 2, 3, 4, 5, 6, 7, 8, 9
Praktyczne przykłady
Przykład 1: Przetwarzanie CSV
def czytaj_csv(nazwa_pliku):
"""Generator wierszy CSV"""
with open(nazwa_pliku, 'r', encoding='utf-8') as plik:
naglowki = next(plik).strip().split(',')
for linia in plik:
wartosci = linia.strip().split(',')
yield dict(zip(naglowki, wartosci))
# Użycie
for rekord in czytaj_csv('dane.csv'):
print(rekord)
# {'Imie': 'Jan', 'Wiek': '25', 'Miasto': 'Warszawa'}
Przykład 2: Batching (grupowanie)
def batches(iterable, rozmiar_batcha):
"""Dzieli iterable na batche o określonym rozmiarze"""
batch = []
for element in iterable:
batch.append(element)
if len(batch) == rozmiar_batcha:
yield batch
batch = []
# Ostatni batch (może być mniejszy)
if batch:
yield batch
# Użycie
dane = range(1, 11) # 1 do 10
for batch in batches(dane, 3):
print(batch)
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]
Przykład 3: Sliding window
def okno_przesuwne(iterable, rozmiar):
"""Generator okna przesuwnego"""
from collections import deque
okno = deque(maxlen=rozmiar)
for element in iterable:
okno.append(element)
if len(okno) == rozmiar:
yield list(okno)
# Użycie
dane = [1, 2, 3, 4, 5, 6, 7]
for okno in okno_przesuwne(dane, 3):
print(okno)
# [1, 2, 3]
# [2, 3, 4]
# [3, 4, 5]
# [4, 5, 6]
# [5, 6, 7]
Przykład 4: Pipeline przetwarzania
def czytaj_liczby(nazwa_pliku):
"""Krok 1: Czytaj liczby z pliku"""
with open(nazwa_pliku, 'r') as plik:
for linia in plik:
yield int(linia.strip())
def filtruj_parzyste(liczby):
"""Krok 2: Filtruj parzyste"""
for liczba in liczby:
if liczba % 2 == 0:
yield liczba
def pomnoz_przez_2(liczby):
"""Krok 3: Pomnóż przez 2"""
for liczba in liczby:
yield liczba * 2
# Pipeline
liczby = czytaj_liczby('liczby.txt')
parzyste = filtruj_parzyste(liczby)
podwojone = pomnoz_przez_2(parzyste)
# Wszystko działa leniwie - przetwarza po jednej liczbie!
for wynik in podwojone:
print(wynik)
Przykład 5: Generator losowych danych
import random
def losowe_liczby(ile, min_val, max_val):
"""Generator losowych liczb"""
for _ in range(ile):
yield random.randint(min_val, max_val)
def losowe_imiona(ile):
"""Generator losowych imion"""
imiona = ['Jan', 'Anna', 'Piotr', 'Ewa', 'Tomasz', 'Kasia']
for _ in range(ile):
yield random.choice(imiona)
# Użycie
for liczba in losowe_liczby(5, 1, 100):
print(liczba)
# 42, 17, 89, 5, 63
for imie in losowe_imiona(3):
print(imie)
# Anna, Piotr, Jan
Send i Close
Generatory mogą również przyjmować wartości!
send()
def kalkulator():
"""Generator jako kalkulator"""
wynik = 0
while True:
operacja = yield wynik
if operacja is None:
break
akcja, liczba = operacja
if akcja == 'dodaj':
wynik += liczba
elif akcja == 'odejmij':
wynik -= liczba
# Użycie
kalk = kalkulator()
next(kalk) # Uruchom generator
print(kalk.send(('dodaj', 10))) # 10
print(kalk.send(('dodaj', 5))) # 15
print(kalk.send(('odejmij', 3))) # 12
close()
def nieskonczone_liczby():
n = 0
try:
while True:
yield n
n += 1
finally:
print("Generator zamknięty - sprzątam")
gen = nieskonczone_liczby()
print(next(gen)) # 0
print(next(gen)) # 1
gen.close() # Generator zamknięty - sprzątam
# print(next(gen)) # StopIteration
Itertools – moduł z generatorami
Python ma wbudowany moduł itertools z potężnymi generatorami:
import itertools
# count - nieskończone liczenie
for i in itertools.count(10, 2):
if i > 20:
break
print(i)
# 10, 12, 14, 16, 18, 20
# cycle - cykliczne powtarzanie
kolory = itertools.cycle(['czerwony', 'zielony', 'niebieski'])
for i, kolor in enumerate(kolory):
if i >= 6:
break
print(kolor)
# czerwony, zielony, niebieski, czerwony, zielony, niebieski
# repeat - powtarzanie n razy
for x in itertools.repeat('Python', 3):
print(x)
# Python, Python, Python
# chain - łączenie iteratorów
for x in itertools.chain([1, 2, 3], ['a', 'b', 'c']):
print(x)
# 1, 2, 3, a, b, c
# islice - wycinki
for x in itertools.islice(range(100), 5, 15, 2):
print(x)
# 5, 7, 9, 11, 13
# combinations - kombinacje
for combo in itertools.combinations([1, 2, 3, 4], 2):
print(combo)
# (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)
# permutations - permutacje
for perm in itertools.permutations([1, 2, 3], 2):
print(perm)
# (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)
Generator vs Iterator
Iterator to obiekt implementujący metody __iter__() i __next__():
class LicznikIterator:
def __init__(self, limit):
self.limit = limit
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.limit:
raise StopIteration
self.current += 1
return self.current
# Użycie
for i in LicznikIterator(5):
print(i)
# 1, 2, 3, 4, 5
Generator to prostsza składnia dla iteratora:
def licznik_generator(limit):
for i in range(1, limit + 1):
yield i
# To samo, krócej!
for i in licznik_generator(5):
print(i)
# 1, 2, 3, 4, 5
Podsumowanie Generatorów
Definicja:
- Funkcja z
yieldzamiastreturn - Zwraca iterator
- Leniwa ewaluacja
Kluczowe słowa:
yield– zwróć wartość i zatrzymajyield from– deleguj do innego generatoranext()– pobierz następną wartośćsend()– wyślij wartość do generatoraclose()– zamknij generator
Zalety:
- Oszczędność pamięci
- Efektywność dla dużych danych
- Nieskończone sekwencje
- Pipeline’y przetwarzania
Zastosowania:
- Przetwarzanie dużych plików
- Strumienie danych
- Pipeline’y transformacji
- Nieskończone sekwencje
Generator expressions:
(x for x in range(10))– jak list comprehension ale z()
Ćwiczenia
Generatory
- Napisz generator liczb od n do m z krokiem k.
- Napisz generator liczb parzystych.
- Napisz generator liczb nieparzystych.
- Napisz generator Fibonacciego do limitu.
- Napisz generator liczb pierwszych.
- Napisz generator kwadratów liczb.
- Napisz generator odwracający string znak po znaku.
- Napisz generator czytający plik linia po linii.
- Napisz generator filtrujący puste linie z pliku.
- Napisz generator countdown (odliczanie).
Generatory – zaawansowane
- Napisz generator spłaszczający zagnieżdżoną listę.
- Napisz generator grupujący elementy w batche.
- Napisz generator okna przesuwnego (sliding window).
- Napisz generator permutacji listy.
- Napisz generator kombinacji elementów.
- Napisz generator kartezjańskiego iloczynu.
- Napisz generator nieskończonych liczb Fibonacciego.
- Napisz generator losowych liczb w zakresie.
- Napisz generator dat między dwoma datami.
- Napisz generator tokenów z tekstu.
Generator expressions
- Stwórz generator expression kwadratów liczb 1-10.
- Stwórz generator expression parzystych z listy.
- Filtruj listę stringów (długość > 5) przez generator.
- Stwórz generator sumujący elementy dwóch list.
- Generator expression dla wielkich liter z tekstu.
Pipeline’y
- Stwórz pipeline: czytaj liczby → filtruj parzyste → pomnóż × 2.
- Pipeline: czytaj plik → filtruj linie ze słowem → zlicz.
- Pipeline: generator liczb → filtruj pierwsze → sumuj.
- Pipeline: czytaj CSV → przekształć dane → filtruj.
- Pipeline: generuj losowe → filtruj zakres → agreguj.
Praktyczne zastosowania
- Generator odczytujący duży plik CSV.
- Generator przetwarzający logi (filtruj ERROR).
- Generator paginacji (strony wyników).
- Generator krokowy (skip n elementów).
- Generator unikalnych elementów.
- Generator zip dla wielu iteratorów.
- Generator grupujący po kluczu.
- Generator akumulujący (cumsum).
- Generator take/drop (weź n, pomiń n).
- Generator iterujący po drzewie katalogów.