Представьте себе функцию, которая возвращает все целые числа от 0 до max_number:
def numbers_up_to(max_number):
output = []
for number in range(max_number + 1):
output.append(number)
return output
Code language: JavaScript (javascript)
В таком виде эта функция создает список [0, 1, 2, 3, 4, …], пока не достигнет max_number. При этом в памяти уже выделяется место, куда сохраняется список и его содержимое. Используя для этого аппаратные ресурсы. Проблемы нет если max_number мал…
… но попробуйте использовать эту функцию с numbers_up_to(623 * 10 ** 21). Нет, не пытайтесь. Ваш компьютер сойдет с ума. Я серьезно.
Для этого у нас есть более эффективная альтернатива: генераторы! Давайте превратим эту функцию в генератор.
Это просто: мы не создаем никакого списка и используем yield вместо return:
def numbers_up_to(max_number):
for number in range(max_number + 1):
yield number
Code language: JavaScript (javascript)
Теперь попробуйте использовать эту функцию с гигантским числом: numbers_up_to_as(623 * 10 ** 21). На этот раз вы можете попробовать. Может быть, ваш компьютер даже не сломается.
Он будет вычислять первый элемент последовательности только тогда, когда ему это нужно. В результате вы получите 1 и остановку. Он больше ничего не обрабатывает, ничего не выделяет в памяти. Ничего. Пока вы не попросите следующий номер. Затем он забывает о первом и дает вам второй. И так далее. Вы просите, и он дает вам по очереди: третий, потом четвертый, потом пятый и так далее. По очереди.
Вместо того чтобы создавать весь список, он создает генератор и вычисляет один за другим элементы, в зависимости от необходимости доступа к ним, и фактически он будет вычислять что-то только при каждом next() – это функция, которая вызывается внутри, если вы передаете генератор, например, в for.
Но next() можно использовать и вручную – что очень удобно для изучения:
my_first_generator = numbers_up_to(42) next(my_first_generator) next(my_first_generator) next(my_first_generator) next(my_first_generator) next(my_first_generator)
В моих примерах даже диапазон, который является родным для Python 3, уже сам по себе является генератором.
Генераторы очень полезны и очень бережно относятся к памяти. Но, конечно, есть некоторые ограничения: например, вы не можете использовать два for в одном генераторе напрямую – генераторы только продвигаются по последовательности, они никогда не возвращаются в ее начало. Поэтому, когда первый for исчерпает генератор, второй for больше не сможет его использовать.