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

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

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

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

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

Замыкания

Замыкания

Помните функцию 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.


<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

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

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

Для полного доступа к курсу нужна профессиональная подписка

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

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

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».

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

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

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

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».