Генераторы в Python
В этой статье объясняются генераторы в Python.
YouTube Video
Генераторы в Python
Обзор
Генераторы в Python — это вид итераторов и мощный инструмент для эффективной обработки повторяющихся операций. Они позволяют писать памяти-экономящий код при работе с большими объемами данных.
Что такое генератор?
Генератор в Python — это специальная функция, которая выдаёт по одному значению за раз, определяемая с помощью ключевого слова yield. Её особенностью является то, что выполнение прерывается с сохранением состояния и может быть возобновлено позже.
Основы yield
yield — это ключевое слово, которое возвращает значение и приостанавливает выполнение функции одновременно.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8print(next(gen)) # 1
9print(next(gen)) # 2
10print(next(gen)) # 3- При вызове такая функция возвращает объект-генератор, который отдаёт значения по одному за раз.
- Если вызвать
next(), когда нет следующего значения, произойдет ошибкаStopIteration.
next() и StopIteration
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8try:
9 while True:
10 value = next(gen)
11 print(value)
12except StopIteration:
13 print("Finished")- Явно обрабатывая ошибку
StopIterationтаким образом, вы можете определить, когда генератор завершил работу.
send(value)
Вызов send(value) возобновляет выполнение генератора и отправляет значение value на позицию выражения yield. Отправленное значение может быть получено на стороне генератора как возвращаемое значение выражения yield. При первом вызове нельзя отправлять ничего, кроме None с помощью send(value), поэтому необходимо использовать next() или send(None).
1def gen():
2 x = yield 1
3 print(f"x = {x}")
4 y = yield 2
5 print(f"y = {y}")
6
7g = gen()
8print(next(g)) # -> 1 (value from yield 1)
9print(g.send(10)) # -> x = 10, 2 (value from yield 2)
10print(g.send(20)) # -> y = 20, StopIteration occurs- При вызове
send(10)выражениеyieldв генераторе становится выражением, возвращающим 10, и 10 присваивается переменнойx.
throw()
Вызов throw возобновляет выполнение генератора и вызывает исключение в месте приостановки на выражении yield. Вы можете обработать исключение внутри генератора, чтобы продолжить выполнение. Если исключение не перехвачено, оно распространяется наружу и генератор завершает выполнение.
1def gen():
2 try:
3 yield 1
4 except ValueError as e:
5 print(f"Caught: {e}")
6 yield "recovered"
7
8g = gen()
9print(next(g)) # -> 1
10print(g.throw(ValueError("boom"))) # -> Caught: boom, "recovered"- В этом коде вызывается
throw, чтобы ввести исключение в генератор. На стороне генератора исключение обрабатывается и возвращается значениеrecovered.
close()
Вызов close() завершает выполнение генератора. Внутри генератора можно выполнить очистку с помощью блока finally. Вызов next() или send() после close() вызывает ошибку StopIteration.
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...- Этот код показывает, что вызов
close()завершает выполнение генератора и запускает процесс очистки в блокеfinally.
yield from
yield from — это синтаксис, используемый для делегирования подгенератору. Это простой способ вызвать другой генератор внутри генератора и передать все его значения во внешний контекст.
1def sub_gen():
2 yield 1
3 yield 2
4
5def main_gen():
6 yield from sub_gen()
7 yield 3
8
9print(list(main_gen())) # -> [1, 2, 3]- Этот код при помощи
yield fromделегирует все значения от подгенератора внешнему генератору, а затем возвращает3.
Связь с итераторами
Генераторы внутри реализуют методы __iter__() и __next__(), что делает их видом итераторов. Поэтому они полностью совместимы с итерируемыми операциями, такими как циклы for.
Использование с циклами for
В Python цикл for внутренне использует next(), чтобы автоматически получать значения.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)Таким образом, обработка StopIteration происходит автоматически.
Создание бесконечных генераторов
1def count_up(start=0):
2 while True:
3 yield start
4 start += 1
5
6counter = count_up()
7print(next(counter)) # 0
8print(next(counter)) # 1Возможно создавать бесконечные циклы, однако использовать их нужно осторожно.
Генераторные выражения
Генераторные выражения, записываемые в скобках, позволяют создавать генераторы с синтаксисом, похожим на списковые включения.
1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8 print(square)В отличие от списковых включений, они не загружают все элементы в память сразу, что делает их более эффективными по памяти.
Обработка ошибок в генераторах
Внутри генератора могут возникать исключения. В таких случаях используется try-except так же, как в обычном коде Python.
1def safe_divide_generator(numbers, divisor):
2 """Yields results of dividing numbers by a given divisor safely."""
3 for number in numbers:
4 try:
5 yield number / divisor # Attempt to divide and yield result.
6 except ZeroDivisionError:
7 yield float('inf') # Return infinity if division by zero occurs.
8
9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0) # Create generator with divisor as 0.
12for value in gen:
13 print(value) # Output: inf, inf, infВ этом примере выполняется правильная обработка ошибки при делении на ноль.
Трассировка стека генератора
Если внутри генератора происходит исключение, оно будет вызвано при возобновлении работы генератора.
1def error_generator():
2 """A generator that yields values and raises an error."""
3 yield 1
4 raise ValueError("An error occurred") # Raise a ValueError intentionally.
5 yield 2
6
7gen = error_generator()
8print(next(gen)) # Output: 1 (first value yielded)
9try:
10 print(next(gen)) # Attempt to get the next value, which raises an error
11except ValueError as e:
12 print(e) # Output: An error occurred (exception message is printed)- Этот генератор сначала возвращает
1. Ошибка, возникшая при возобновлении, перехватывается и выводится как сообщение об ошибке.
Примеры использования генераторов
Чтение файла построчно (подходит для больших файлов)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()- Эта функция построчно читает текстовый файл с помощью итератора, удаляет пробелы в начале и в конце каждой строки и возвращает их в виде генератора, что позволяет обрабатывать большие файлы с низким потреблением памяти.
Генератор для последовательности Фибоначчи
1def fibonacci(limit):
2 a, b = 0, 1
3 while a < limit:
4 yield a
5 a, b = b, a + b
6
7for n in fibonacci(100):
8 print(n)- Этот код использует генератор для последовательной генерации чисел Фибоначчи, меньших верхнего предела, и выводит их с помощью цикла
for.
Сценарии применения
Генераторы также могут использоваться в следующих случаях.
- Последовательная обработка больших CSV или лог-файлов
- Постраничная обработка API
- Обработка потоковых данных (например, Kafka, IoT-устройства)
Резюме
| Понятие | Ключевой момент |
|---|---|
yield |
Приостанавливает выполнение и возвращает значение |
| Функция-генератор | Функция, содержащая yield и возвращающая итератор при вызове |
| Преимущества | Экономно используется память, идеально для работы с большими наборами данных |
| Генераторное выражение | Позволяет писать лаконичный синтаксис вроде (x for x in iterable) |
Используя генераторы, вы можете эффективно обрабатывать большие объемы данных, экономя память и сохраняя ваш код лаконичным.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.