generator - це функція або вираз, що повертає особливий тип ітератора, який називається генераторний ітератор.
Generator-iterators є лінивими: вони не зберігають свої values у памʼяті, а генерують значення тоді, коли потрібно.
Функція-генератор має такий самий вигляд, як і будь-яка інша функція, але містить один або кілька виразів yield.
Кожен yield зупиняє виконання коду, зберігаючи поточний стан виконання (разом з усіма локальними змінними та інструкціями try).
Коли генератор відновлює роботу, він продовжує зі стану, збереженого під час зупинки, на відміну від звичайних функцій, які скидаються з кожним викликом.
Генератори створюють майже так само, як інші функції з циклами чи рекурсією, але вони вимагають виразу yield, який ми докладніше розглянемо трохи згодом.
Приклад - функція, яка повертає квадрати із заданого масиву чисел.
У наведеному вигляді всі вхідні дані потрібно обробити, перш ніж можна буде повернути якісь значення:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
Перетворити цю функцію на генератор можна так:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
Сенс у тому, що генератор використовують тоді, коли всі значення не потрібні одразу.
Це заощаджує памʼять і обчислювальні ресурси, адже обчислюється лише те значення, з яким ми працюємо зараз.
Генератори (технічно generator-iteratorів, див. примітку нижче) - це різновид iterator, і їх можна використовувати будь-де в Python, де очікується iterator або iterable.
Це стосується функцій або обʼєктів, які приймають iterable/iterator як аргумент.
Щоб зануритися глибше, див. How to Make an Iterator in Python.
Генераторні ітератори - це особливий підвид ітераторів.
Iterators - це механізм/протокол, який уможливлює перебирання ітерабельних обʼєктів.
Генераторні ітератори та ітератори, які повертають звичайні iterables у Python, поводяться дуже схоже, але є кілька важливих відмінностей, які варто зауважити:
indexes.
Не можна звернутися до попереднього чи наступного значення за допомогою додавання чи віднімання, і не можна використовувати запис у квадратних дужках ([]) чи зрізи.len(), бо вони не мають довжини.generator-iterator!Щоб скористатися генератором squares_generator():
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
Значення в генераторі також можна створювати й отримувати за допомогою функції next().
next() викликає метод __next__() обʼєкта-генератора, «просуваючи» або обчислюючи код генератора до його виразу yield, який потім «віддає» чи повертає значення.
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
Коли generator повністю вичерпано і він більше не має значень для повернення, він викидає помилку StopIteration.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Генератори - це особливий підвид ітераторів.
Iterators - це механізм/протокол, який уможливлює перебирання ітерабельних обʼєктів.
Генератори й ітератори, які повертають звичайні iterables у Python, поводяться дуже схоже, але є кілька важливих відмінностей, які варто зауважити:
Генератори односторонні: повернутися до попереднього значення не можна.
Перебирання генераторів споживає повернені значення; скидання немає.
Генератори (оскільки обчислюються ліниво) не можна впорядкувати й не можна розвернути.
Генератори не мають indexes, тож не можна звернутися до попереднього чи наступного значення за допомогою додавання чи віднімання.
Генератори не можна використовувати з функцією len().
Генератори можуть бути скінченними або нескінченними, тож будьмо обережні, збираючи всі значення з нескінченного генератора.
yield
Вираз yield дуже схожий на вираз return.
На відміну від виразу return, yield віддає значення тому, хто викликав, у певній точці, призупиняючи обчислення й повернення подальших значень, доки їх не запросять.
Коли yield обчислюється, він зупиняє виконання функції, у якій міститься, і повертає значення функції на той момент.
Після цього функція залишається в області видимості, і коли викликається __next__(), виконання відновлюється, доки знову не трапиться yield.
Використовувати вирази yield поза функціями заборонено.
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
Генератори стають у пригоді в багатьох задачах.
Працюючи з великою колекцією, ми можемо не хотіти тримати всі її значення в memory.
Генератор дає змогу опрацьовувати великі дані частина за частиною, заощаджуючи памʼять і підвищуючи швидкодію.
Генератори також дуже допомагають, коли процес чи обчислення складні, дорогі або нескінченні:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
Тепер щоразу, коли для обʼєкта infinite_sequence викликають __next__(), він повертає попереднє число + 1.