Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Ленивые вычисления Python: Функции

Lazy Evaluation

Lazy Evaluation — это ленивые вычисления или отложенные вычисления, которые применяются в некоторых языках программирования. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.

В реальности это касается не только языков. Это применяется и на уровне библиотек. Сейчас мы увидим, зачем это нужно, и как это работает.

Ленивость в том или ином виде существует во всех языках программирования. В основном это касается логических выражений. И в Python она тоже есть.

Например, если мы встречаем такое логическое выражение, то его выполнение идет слева направо:

# True
True or print('message')

Если мы проверяем True, и далее стоит оператор "или" (or), то нам неважно, что будет справа. Эта часть кода не повлияет на то, что результатом будет истина.

Это такая стратегия оптимизации внутри, которая позволяет не вычислять правое значение. Программисты пользуются этим, чтобы проверять существование какого-то объекта, например, что он не равен None, и вызывать дальше какой-то метод. Это проверка на существование позволяет не писать сложные куски кода.

Работа с коллекциями

Основная область применения ленивых вычислений - работа с коллекциями. В Python есть встроенная функция reversed():

nums = [1, 2, 3, 4, 5]
reversed(nums) # <list_reverseiterator at 0x75e0455d73d0>

Вместо списка функция вернула объект итератор. Итераторы это особые объекты, представляющие собой поток данных. С понятием потока данных (или стримами "stream") вы будете также встречаться в других языках, и везде они используются для ленивых вычислений. Потоки особенно важны при работе с большими объемами данных или при обработке данных в режиме реального (потокового) времени. Потоки позволяют обрабатывать элементы по запросу, а не загружать все данные сразу в память.

Получать элементы из итератора можно функцией next(), либо самое простое, обойти их в цикле. Иначе говоря, итерироваться.

nums = [1, 2, 3, 4, 5]

for elem in reversed(nums):
    print(elem)

# => 5
# => 4
# => 3
# => 2
# => 1

В действительности, при использовании цикла for .. in у коллекции запрашивается метод __iter__(), возвращающий итератор. А на каждом шаге итерации вызывается метод __next__(), возвращающий следующий элемент коллекции. Также Python позволяет получать объект итератора явно, используя функцию iter().

В примере выше reversed() не возвращает сразу новую перевернутую коллекцию, а создает итератор, который уже по запросу отдает по одному элементу с конца коллекции. Так можно в целом описать концепцию ленивых вычислений - не вычислять ничего, пока не нужно. Таким образом мы экономим вычислительные ресурсы, можем обрабатывать очень большие объемы данных (и даже "бесконечные"), а еще выстраивать цепочку вычислений и запускать ее по требованию.

При работе с итераторами важно помнить, что по итератору можно двигаться лишь в одном направлении пока итератор не исчерпает себя. Также если мы прервем обработку и вернемся к итератору вновь, то обработка продолжится с места остановки.

numbers = [1, 2, 3, 4, 5]

# получим итератор явно
it = iter(numbers)

for elem in it:
    print(elem)
    # прервем выполнение
    if elem == 3:
        break

# => 1
# => 2
# => 3

# и вернемся позже
for elem in it:
    print(elem)

# => 4
# => 5

Генераторы

Помимо встроенных функций как reversed(), возвращающих итераторы, в Python есть инструменты для создания своих ленивых функций.

def gen_squares_to(n):
    i = 1
    while i <= n:
        yield i ** 2
        i += 1

for n in gen_squares_to(5):
    print(n)

# => 1
# => 4
# => 9
# => 16
# => 25

Подобные функции, которые могут вести себя как итераторы и на каждом шаге итерации генерируют новое значение, называют генераторами.

Определение функции выше похоже на привычное нам за исключением нового слова yield, которое используется вместо return. Основное отличие генераторных функций, что после исполнения yield, функция не завершается, а приостанавливается до нового шага итерации. Это позволяет исполнять код после выражения yield, использовать несколько yield и даже реализовывать бесконечные потоки данных:

def gen_squares():
    i = 1
    # бесконечный цикл
    while True:
        yield i ** 2
        i += 1

result = []
for num in gen_squares():
    result.append(num)
    if num > 100:
        break

print(result) # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

Выводы

В этом уроке мы узнали, что такое Lazy Evaluation. Это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.

Также мы узнали, как ленивые вычисления помогают при работе с коллекциями. Узнали почему функции для обработки коллекций возвращают итераторы, потоки данных. Научились создавать собственные функции генерирующие потоки данных. Ленивые вычисления и потоковая обработка это ключевые инструменты в работе с большими данными.


Дополнительные материалы

  1. Lazy evaluation

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 21 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

Задавайте вопросы, если хотите обсудить теорию или упражнения. Команда поддержки Хекслета и опытные участники сообщества помогут найти ответы и решить задачу