Помните функцию 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.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт