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

Замыкания Python: Функции

Помните функцию inner, которую мы создавали в первом уроке по ФВП? Так вот, эта функция была замыканием! Замыкание (closure) — это такая функция, которая ссылается на локальные переменные (использует их в своём теле) в области видимости, в которой она была создана. Этим замыкание отличается от обычной функции, которая может использовать только свои аргументы и глобальные переменные. Рассмотрим пример, демонстрирующий замыкание, и уже на нём разберём что и чем является:

G = 10

def make_closure():
    a = 1
    b = 2
    def inner(x):
        return x + G + a
    return inner

В этом примере inner — замыкание. Переменная b не используется в теле inner и замыканием запомнена не будет. А вот переменная a, напротив, участвует в формировании результата вызова inner и поэтому её значение будет запомнено. А вот G, это глобальная переменная (если принять факт, что указанный код находится на верхнем уровне модуля, т.е. не вложен ни в какие другие блоки).

Кстати, функции, создаваемые на верхнем уровне модуля, т.е. не внутри каких-либо других функций, замыканиями не являются — им просто нечего замыкать, ведь все видимые таким функциям внешние имена (импорты и другие определения из этого модуля, находящиеся выше по строчкам кода) являются глобальными.

Момент запоминания значений переменных

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

def make_closure():
    y = 1
    def inner(x):
        return x + y
    y = 42
    return inner

make_closure()(100)
# 142

Здесь inner получает в качестве запомненного значения 42, пусть даже присваивание этого значения переменной y происходит и после объявления функции! Ещё "забавнее" выглядит замыкание переменной цикла:

printers = []
for i in range(10):
    def printer():
        print(i)
    printers.append(printer)

printers[0]()
# 9
printers[5]()
# 9
printers[9]()
# 9
i = 42
printers[0]()
# 42

Казалось бы, мы создали десяток функций, каждая из которых должна печатать своё число, но все функции печатают последнее значение переменной цикла! Здесь тоже фактическое запоминание происходит при выходе из области видимости в которой определена переменная i, вот только эта область видимости на момент вызова замыканий ещё не завершилась (в этом REPL-примере она завершится только при выходе из REPL)! Поэтому после выхода из цикла все замыкания выводят 9, а после изменения значения переменной i выводимое значение также меняется!

Борем замыкания!

Как же запоминать то, что нужно и когда нужно? Как же нам починить пример с циклом, чтобы каждая функция печатала своё значение и не реагировала на дальнейшие изменения переменной цикла? Отвечаю: нужно замкнуть переменную в области видимости, которая завершится сразу же после создания замыкания! Этого можно добиться, завернув создание функции в… другую функцию! Вот код:

printers = []
for i in range(10):
    def make_printer(arg):
        def printer():
            print(arg)
        return printer
    p = make_printer(i)
    printers.append(p)

printers[0]()
# 0
printers[5]()
# 5

Результат положительный! Но как же этот код работает? Заметьте, в этот раз printer замыкает значение переменной arg, а она принадлежит функции make_printer и видна только пока выполняется тело функции. А ведь это именно то, что нам нужно: когда происходит выход из тела make_printer, возвращаемое замыкание получает-таки своё значение. А раз функция make_printer вызывается с разными аргументами, то и замыкания получают разные значения!

Эта техника "завернуть в функцию и тут же вызвать" не является эксклюзивным для Python "костылём": она применяется и во многих других языках, реализующих механизм замыканий. Например, точно так же поступают программисты на JavaScript.


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

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

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

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.

Об обучении на Хекслете

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Python-разработчик
Профессия
Разработка веб-приложений на Django
25 мая 10 месяцев

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

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

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

Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»