Generatory przydają się do pracy z dużymi zbiorami danych, które nie mieszczą się w pamięci komputera. Natomiast sam muszę przyznać, że w mojej pracy zawodowej nie przetwarzałem tak dużych zbiorów danych bezpośrednio w Pythonie, aby mieć okazję wykorzystać generator.

Z czasem zrozumiałem, że stosowanie generatora tak naprawdę nie wymaga przetwarzania danych na dużą skalę. O tym właśnie w dzisiejszym wpisie.

Na samym początku warto porównać zapis funkcji i generatora:

def select_odd_numbers(xs):
    results = []
    for x in xs:
        if x % 2 == 1:
            results.append(x)
    return results
def select_odd_numbers(xs):
    for x in xs:
        if x % 2 == 1:
            yield x

Pierwszą znaczącą różnicą w tym przykładzie jest fakt, że wywołanie funkcji pociąga za sobą wykonanie całego kodu. Nie można przerwać wykonania funkcji od zewnątrz, ponieważ jest ona niepodzielna.

Dodatkowo warto zwrócić uwagę, że powyższa funkcja na czas wykonania, musi buforować wynik swojej pracy w pomocniczej liście.

Przypadek generatora jest odmienny, ponieważ jego praca jest podzielona na etapy. Przejście przez nie wymaga korzystania z funkcji next. To sprawia, że generator nie musi ani buforować wyników ani realizować w całości swojej pracy.

Znając zasadniczą różnicę między funkcją, a generatorem możemy łatwiej przeanalizować poniższy przykład:

for x in select_odd_numbers(numbers):
    if x == 13:
        break

W przypadku, gdy select_odd_numbers jest generatorem, break powoduje nie tylko przerwanie pętli, ale również kończy pracę generatora. Liczba 13 jest ostatnią liczbą jaką wyprodukuje generator.

Użycie  select_odd_numbers jako funkcji w tej sytuacji nie jest zasadne, ponieważ przed wykonaniem pętli, kod funkcji musi ukończyć się wcześniej, co w wielu przypadkach może okazać się niewydajne.

Generatory z racji leniwego wykonania, mogą wyodrębnić część działań z pętli, bez uszczerbku na jej wydajności.