24. Python – Generatory


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 yield zamiast return
  • Zwraca iterator
  • Leniwa ewaluacja

Kluczowe słowa:

  • yield – zwróć wartość i zatrzymaj
  • yield from – deleguj do innego generatora
  • next() – pobierz następną wartość
  • send() – wyślij wartość do generatora
  • close() – 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

  1. Napisz generator liczb od n do m z krokiem k.
  2. Napisz generator liczb parzystych.
  3. Napisz generator liczb nieparzystych.
  4. Napisz generator Fibonacciego do limitu.
  5. Napisz generator liczb pierwszych.
  6. Napisz generator kwadratów liczb.
  7. Napisz generator odwracający string znak po znaku.
  8. Napisz generator czytający plik linia po linii.
  9. Napisz generator filtrujący puste linie z pliku.
  10. Napisz generator countdown (odliczanie).

Generatory – zaawansowane

  1. Napisz generator spłaszczający zagnieżdżoną listę.
  2. Napisz generator grupujący elementy w batche.
  3. Napisz generator okna przesuwnego (sliding window).
  4. Napisz generator permutacji listy.
  5. Napisz generator kombinacji elementów.
  6. Napisz generator kartezjańskiego iloczynu.
  7. Napisz generator nieskończonych liczb Fibonacciego.
  8. Napisz generator losowych liczb w zakresie.
  9. Napisz generator dat między dwoma datami.
  10. Napisz generator tokenów z tekstu.

Generator expressions

  1. Stwórz generator expression kwadratów liczb 1-10.
  2. Stwórz generator expression parzystych z listy.
  3. Filtruj listę stringów (długość > 5) przez generator.
  4. Stwórz generator sumujący elementy dwóch list.
  5. Generator expression dla wielkich liter z tekstu.

Pipeline’y

  1. Stwórz pipeline: czytaj liczby → filtruj parzyste → pomnóż × 2.
  2. Pipeline: czytaj plik → filtruj linie ze słowem → zlicz.
  3. Pipeline: generator liczb → filtruj pierwsze → sumuj.
  4. Pipeline: czytaj CSV → przekształć dane → filtruj.
  5. Pipeline: generuj losowe → filtruj zakres → agreguj.

Praktyczne zastosowania

  1. Generator odczytujący duży plik CSV.
  2. Generator przetwarzający logi (filtruj ERROR).
  3. Generator paginacji (strony wyników).
  4. Generator krokowy (skip n elementów).
  5. Generator unikalnych elementów.
  6. Generator zip dla wielu iteratorów.
  7. Generator grupujący po kluczu.
  8. Generator akumulujący (cumsum).
  9. Generator take/drop (weź n, pomiń n).
  10. 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ętli for. Działa do najbliższego yield, zwraca wartość i zatrzymuje się, zachowując zmienne lokalne.
  • return kończy funkcję na stałe. yield ją tylko wstrzymuje, a kolejne next() wznawia od miejsca zatrzymania.
  • Gdy generator się skończy, next() zgłasza StopIteration. Pętla for obsł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() w for ... in range(...) albo break).
  • 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 wynik yield). Przed pierwszym send() generator trzeba uruchomić przez next().
  • close() zamyka generator. Kod w finally jeszcze 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:

  • return zamiast yield w funkcji, która ma być generatorem
  • próba drugiego użycia wyczerpanego generatora
  • pętla for po generatorze nieskończonym bez break
  • send() na generatorze, który nie został jeszcze uruchomiony przez next()
  • 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:

  1. Funkcja z yield to generator. Samo wywołanie niczego nie wykonuje, zwraca obiekt generatora.
  2. yield zatrzymuje funkcję i zapamiętuje jej stan. return ją kończy.
  3. Wartości pobierasz przez next(gen) albo pętlą for. Po wyczerpaniu next() zgłasza StopIteration.
  4. Generator można przejść tylko raz.
  5. (x for x in ...) to generator, [x for x in ...] to lista.
  6. Generator nieskończony wymaga break albo ograniczenia liczby wywołań next().
  7. Generator oszczędza pamięć, bo liczy wartości „na żądanie”.
  8. Iterator = __iter__ + __next__. Generator to jego krótszy zapis.
  9. Przed pierwszym send() trzeba wywołać next().

Ściągawka – najważniejsze polecenia i składnia

KonstrukcjaZnaczenie
yield wartosczwróć 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 gen2przekaż 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.