Szybki quiz – przypomnienie poprzedniej lekcji
Pytanie 1. Co zostanie wyświetlone?
def stworz_mnoznik(n):
def mnoz(x):
return x * n
return mnoz
razy_3 = stworz_mnoznik(3)
print(razy_3(4))
Odpowiedź
Odpowiedź: 12. Funkcja mnoz „pamięta” n = 3, mimo że stworz_mnoznik już się zakończyła.
Pytanie 2. Co zostanie wyświetlone?
def licznik(start=0):
count = start
def increment():
nonlocal count
count += 1
return count
return increment
a = licznik(0)
b = licznik(10)
print(a(), a(), b())
Odpowiedź
Odpowiedź: 1 2 11. Każde wywołanie licznik() tworzy osobne domknięcie z własnym count.
Pytanie 3. Dlaczego ten kod zgłosi błąd? Jak go naprawić?
def licznik():
count = 0
def increment():
count += 1
return count
return increment
f = licznik()
f()
Odpowiedź
Odpowiedź: Bez nonlocal Python traktuje count w increment jako zmienną lokalną, której nie ma jeszcze wartości (UnboundLocalError). Naprawa: dodać nonlocal count na początku increment.
Pytanie 4. Co się stanie, jeśli w funkcji zewnetrzna zamiast return wewnetrzna napiszemy return wewnetrzna()?
def zewnetrzna():
wiadomosc = "Hello"
def wewnetrzna():
print(wiadomosc)
return wewnetrzna()
f = zewnetrzna()
f()
Odpowiedź
Odpowiedź: zewnetrzna() od razu wywoła wewnetrzna() (wyświetli Hello), a zwróci None. Zmienna f zawiera więc None, a f() zgłosi TypeError: 'NoneType' object is not callable. Żeby powstało domknięcie, trzeba zwrócić funkcję bez nawiasów.
Pytanie 5. Uzupełnij kod, aby kwadrat(5) zwróciło 25:
def stworz_potege(wykladnik):
def poteguj(podstawa):
return ______
return ______
kwadrat = stworz_potege(2)
Odpowiedź
Odpowiedź: podstawa ** wykladnik oraz poteguj.
Pytanie 6. Podaj trzy elementy, które muszą wystąpić, żeby powstało domknięcie (closure).
Odpowiedź
Odpowiedź: (1) funkcja zagnieżdżona, (2) funkcja wewnętrzna używa zmiennych z funkcji zewnętrznej, (3) funkcja zewnętrzna zwraca funkcję wewnętrzną.
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.
PODSUMOWANIE
- Generator to funkcja zawierająca
yield. Wywołanie takiej funkcji nie wykonuje jej kodu – zwraca obiekt generatora. - Kod generatora wykonuje się dopiero przy
next()albo w pętlifor. Działa do najbliższegoyield, zwraca wartość i zatrzymuje się, zachowując zmienne lokalne. returnkończy funkcję na stałe.yieldją tylko wstrzymuje, a kolejnenext()wznawia od miejsca zatrzymania.- Gdy generator się skończy,
next()zgłaszaStopIteration. Pętlaforobsługuje to sama i po prostu kończy działanie. - Generator jest jednorazowy. Po wyczerpaniu nie da się go „przewinąć”, trzeba wywołać funkcję generatora ponownie.
- Generator jest leniwy: wartości powstają dopiero wtedy, gdy ktoś o nie poprosi. Dzięki temu nie zajmuje pamięci proporcjonalnie do liczby elementów.
- Wyrażenie generatorowe ma
()zamiast[]:(x**2 for x in range(10)). Wynikiem jest generator, a nie krotka z wartościami. - Generator nieskończony (
while True+yield) jest bezpieczny tylko wtedy, gdy pobieramy z niego ograniczoną liczbę wartości (next()wfor ... in range(...)albobreak). yield from inny_generator()przekazuje wszystkie wartości z innego generatora (też w rekurencji, np. spłaszczanie listy).- Generatory łączy się w pipeline: wynik jednego generatora jest wejściem następnego. Każdy element przechodzi przez wszystkie kroki po kolei.
send()wysyła wartość do generatora (wartość pojawia się jako wynikyield). Przed pierwszymsend()generator trzeba uruchomić przeznext().close()zamyka generator. Kod wfinallyjeszcze się wykona.- Iterator to obiekt z metodami
__iter__()i__next__(). Generator to prostszy zapis iteratora, bez pisania klasy.
Typowe źródła błędów:
returnzamiastyieldw funkcji, która ma być generatorem- próba drugiego użycia wyczerpanego generatora
- pętla
forpo generatorze nieskończonym bezbreak send()na generatorze, który nie został jeszcze uruchomiony przeznext()- pomylenie
[...](lista) z(...)(generator)
Minimum, które powinno wejść w pamięć
Jeżeli masz zapamiętać tylko kilka rzeczy z tej lekcji, zapamiętaj te:
- Funkcja z
yieldto generator. Samo wywołanie niczego nie wykonuje, zwraca obiekt generatora. yieldzatrzymuje funkcję i zapamiętuje jej stan.returnją kończy.- Wartości pobierasz przez
next(gen)albo pętląfor. Po wyczerpaniunext()zgłaszaStopIteration. - Generator można przejść tylko raz.
(x for x in ...)to generator,[x for x in ...]to lista.- Generator nieskończony wymaga
breakalbo ograniczenia liczby wywołańnext(). - Generator oszczędza pamięć, bo liczy wartości „na żądanie”.
- Iterator =
__iter__+__next__. Generator to jego krótszy zapis. - Przed pierwszym
send()trzeba wywołaćnext().
Ściągawka – najważniejsze polecenia i składnia
| Konstrukcja | Znaczenie |
|---|---|
yield wartosc | zwróć wartość i wstrzymaj funkcję |
next(gen) | pobierz następną wartość (po końcu: StopIteration) |
for x in gen: | pobiera wartości aż do końca generatora |
(wyrazenie for x in ...) | wyrażenie generatorowe |
yield from gen2 | przekaż wartości z innego generatora |
gen.send(wartosc) | wyślij wartość do generatora (najpierw next(gen)) |
gen.close() | zamknij generator |
Szablon generatora ograniczonego:
def nazwa(limit):
for i in range(limit):
yield i
Szablon generatora filtrującego:
def filtr(dane):
for x in dane:
if warunek:
yield x
Szablon generatora nieskończonego (zawsze z ograniczeniem po stronie użycia):
def nieskonczony():
n = 0
while True:
yield n
n += 1
gen = nieskonczony()
for _ in range(5):
print(next(gen))
Szablon pipeline’u:
dane = zrodlo()
przefiltrowane = filtr(dane)
wynik = przeksztalc(przefiltrowane)
for x in wynik:
print(x)
Odczyt pliku generatorem:
def czytaj_linie(nazwa_pliku):
with open(nazwa_pliku, 'r', encoding='utf-8') as plik:
for linia in plik:
yield linia.strip()
itertools (z lekcji): count, cycle, repeat, chain, islice, combinations, permutations. count, cycle i repeat są nieskończone, trzeba je ograniczyć.
Quiz – sprawdź się
Pytanie 1. Czym różni się yield od return? Co dzieje się ze zmiennymi lokalnymi funkcji po yield?
Pytanie 2. Co zostanie wyświetlone?
def g():
print("A")
yield 1
print("B")
yield 2
gen = g()
print("start")
print(next(gen))
Pytanie 3. Dlaczego ten kod nie działa jak generator? Jak go poprawić?
def kwadraty(n):
for i in range(n):
return i ** 2
for k in kwadraty(3):
print(k)
Pytanie 4. Co zostanie wyświetlone?
gen = (x for x in range(2))
print(next(gen))
print(next(gen))
print(next(gen))
Pytanie 5. Co zostanie wyświetlone?
a = [x for x in range(3)]
b = (x for x in range(3))
print(a)
print(b)
Pytanie 6. Uzupełnij warunek, aby generator zwracał liczby nieparzyste od 0 do n:
def nieparzyste(n):
for i in range(n + 1):
if ______:
yield i
Pytanie 7. Dany jest generator:
def liczby_naturalne():
n = 0
while True:
yield n
n += 1
a) Co wyświetli poniższy fragment?
gen = liczby_naturalne()
next(gen)
next(gen)
print(next(gen))
b) Dlaczego ten fragment nigdy się nie zakończy? Jak zmienić kod, żeby wypisał tylko 5 pierwszych liczb?
for x in liczby_naturalne():
print(x)
Pytanie 8. Co zostanie wyświetlone?
def a():
yield 1
yield 2
def b():
yield from a()
yield 3
for x in b():
print(x)
Pytanie 9. Zadanie praktyczne: napisz generator dlugie_slowa(slowa, min_dlugosc), który zwraca tylko te słowa z listy, które są dłuższe niż min_dlugosc. Co wyświetli poniższe użycie dla Twojego generatora?
for s in dlugie_slowa(["kot", "komputer", "pies", "program"], 4):
print(s)
Pytanie 10. W której sytuacji generator jest lepszym wyborem niż lista?
- A) Potrzebujesz odwołać się do trzeciego elementu:
wynik[3]. - B) Przetwarzasz bardzo duży plik tekstowy linia po linii.
- C) Musisz kilka razy przejść po tych samych danych.
Pytanie 11. Jakie dwie metody musi mieć obiekt, żeby był iteratorem? Który z dwóch zapisów (klasa z tymi metodami czy funkcja z yield) jest krótszy i jak się nazywa?
Pytanie 12. Dlaczego w kodzie kalkulatora z lekcji przed pierwszym send() wywołujemy next(kalk)? Co się stanie, jeśli tego nie zrobimy?
kalk = kalkulator()
next(kalk)
print(kalk.send(('dodaj', 10)))
Odpowiedzi
1. return kończy funkcję na stałe, yield zwraca wartość i tylko wstrzymuje funkcję. Zmienne lokalne są zachowane, a kolejne next() wznawia działanie od miejsca zatrzymania.
2. Wyświetli się:
start
A
1
Wywołanie g() niczego nie uruchamia. Kod rusza dopiero przy next(gen) i zatrzymuje się na pierwszym yield, więc "B" nie pojawi się wcale.
3. Funkcja używa return, więc kończy się przy pierwszej iteracji i zwraca zwykłą liczbę 0. Pętla for próbuje iterować po liczbie i zgłasza TypeError: 'int' object is not iterable. Naprawa: zamienić return i ** 2 na yield i ** 2. Wynik: 0, 1, 4.
4. Wyświetli się 0, potem 1, a trzecie next() zgłosi StopIteration, bo generator jest wyczerpany.
5.
[0, 1, 2]
<generator object <genexpr> at 0x...>
(adres w pamięci będzie inny). Wyrażenie w () tworzy generator, a nie gotowe wartości.
6. i % 2 != 0 (może być też i % 2 == 1). Wynik dla n = 7: 1, 3, 5, 7.
7. a) 2. Wartości to 0, 1, 2, a print dotyczy trzeciego next().
b) Generator ma while True, więc nigdy się nie kończy, a pętla for nie ma warunku zatrzymania. Naprawa, np.:
for x in liczby_naturalne():
if x >= 5:
break
print(x)
albo:
gen = liczby_naturalne()
for _ in range(5):
print(next(gen))
8. 1, 2, 3 (każda w osobnej linii). yield from a() przekazuje wartości z a, a potem b zwraca własne 3.
9.
def dlugie_slowa(slowa, min_dlugosc):
for slowo in slowa:
if len(slowo) > min_dlugosc:
yield slowo
Wynik: komputer, program (słowo pies ma dokładnie 4 litery, więc nie jest dłuższe niż 4).
10. B. Generator czyta jedną linię naraz, więc nie trzyma w pamięci całego pliku. W A potrzebny jest dostęp do elementu po indeksie, którego generator nie daje. W C generator wyczerpuje się po pierwszym przejściu, więc trzeba by go tworzyć od nowa albo użyć listy.
11. Iterator musi mieć __iter__() oraz __next__() (po końcu zgłasza StopIteration). Krótszy jest zapis z yield, czyli generator. Python sam tworzy z niego iterator.
12. next(kalk) uruchamia generator i doprowadza go do pierwszego yield. Dopiero wtedy może on odebrać wartość z send(). Bez tego send(('dodaj', 10)) na świeżym generatorze zgłosi TypeError: can't send non-None value to a just-started generator.