Курс по Python: https://stepik.org/course/100707
На этом занятии затронем новую тему – замыкания. Это один из любимых вопросов на
собеседовании в области программирования – рассказать, что такое замыкания. И
сейчас вы узнаете подробный ответ на него.
Как мы с вами
видели на предыдущем занятии, функции можно объявлять внутри других функций.
Например,
так:
def say_name(name):
def say_goodbye():
print("Don't say me goodbye, " + name + "!")
say_goodbye()
А, затем
вызвать:
Мы здесь сначала
вызываем внешнюю функцию, затем, в ней формируется вложенная функция say_goodbye() и вызывается.
В результате, в консоли видим сообщение «Don't say me goodbye, Sergey!».
Я, думаю, здесь
вам все должно быть понятно. А теперь сделаем, следующее. Вместо вызова
внутренней функции возвратим ссылку на нее с помощью оператора return:
def say_name(name):
def say_goodbye():
print("Don't say me goodbye, " + name + "!")
return say_goodbye
Конечно, после
запуска программы мы не увидим никакого сообщения, так как внутренняя функция
нигде не вызывается. Исправим это. Сохраним ссылку на функцию say_goodbye() в переменной f:
А, затем,
вызовем ее:
Мы снова видим
то же самое сообщение. Вам не кажется здесь ничего странным? Например, откуда
функция say_goodbye() берет
значение переменной name? Ведь внешняя функция say_name() выполнилась и
завершилась, а значит, все ее локальные переменные вроде как тоже должны были
бы исчезнуть? Но нет, мы обращаемся к переменной name и успешно
получаем ее значение! Почему? Давайте разберемся.
Дело в том, что
когда у нас имеется глобальная ссылка f на внутреннее,
локальное окружение функции say_goodbye(), то это
окружение продолжает существовать, оно не удаляется автоматически сборщиком
мусора, именно из-за этой глобальной ссылки на него. А вместе с ним, продолжают
существовать и все внешние локальные окружения, в данном случае – окружение
функции say_name(), потому что также существует неявная, скрытая ссылка на него
из внутреннего окружения. Такие ссылки формируются автоматически и позволяют, в
частности, обращаться к переменным, объявленным в этих внешних окружениях.
Именно поэтому функция print() в say_goodbye() имеет доступ
к переменной name и эта
переменная продолжает существовать, пока существует окружение say_goodbye, а значит и
окружение say_name.
Вот такой
эффект, когда мы «держим» внутреннее локальное окружение и имеем возможность
продолжать использовать переменные из внешних окружений, в программировании
называется замыканием. Замыкание в том смысле, что мы держим внутреннее
окружение say_goodbye переменной f из глобального
окружения. Получается цепочка ссылок, замыкающаяся на глобальном окружении. Мало
того, при каждом новом вызове внешней функции, формируется свое новое,
независимое локальное окружение, со своими локальными переменными и
соответствующими значениями:
f = say_name("Sergey")
f2 = say_name("Python")
f()
f2()
Где может
пригодиться такой функционал? Например, можно создать функцию-счетчик, которая
бы увеличивала значение локальной переменной на единицу при каждом запуске:
def counter(start=0):
def step():
nonlocal start
start += 1
return start
return step
Обратите
внимание, мы здесь используем ключевое слово nonlocal, чтобы
переменная start изменялась во
внешней локальной области, а не создавалась бы в текущей, локальной. Без этой
строчки возникнет ошибка, из-за неопределенности: мы берем текущее значение start извне, а потом создавали
бы переменную с тем же именем внутри области step. Так делать
нельзя. И строчка nonlocal start четко указывает брать переменную start из внешней
локальной области, а не создавать в текущей.
Теперь можно сформировать
несколько таких независимых счетчиков и выполнить их:
c1 = counter(10)
c2 = counter()
print(c1(), c2())
print(c1(), c2())
print(c1(), c2())
У нас,
действительно, оба счетчика отработают независимо друг от друга.
И приведу еще
один пример с замыканиями. Предположим, мы хотим сделать функцию, которая бы
удаляла ненужные символы в начале и конце строки. Через замыкание это можно
реализовать, следующим образом:
def strip_string(strip_chars=" "):
def do_strip(string):
return string.strip(strip_chars)
return do_strip
Обратите
внимание, вложенная функция тоже имеет параметр – строку, у которой будут
удаляться ненужные символы. Далее, создадим два объекта с разным списком
удаляемых символов:
strip1 = strip_string()
strip2 = strip_string(" !?,.;")
И вызовем
вложенную функцию с одним аргументом:
print(strip1(" hello python!.. "))
print(strip2(" hello python!.. "))
Смотрите, первая
функция strip1 убрала только
пробелы, а вторая еще и восклицательный знак с точками. Таким образом, мы можем
многократно использовать в программе функции strip1 и strip2, передавая им
разные строки.
Вот, что из себя
представляют замыкания и вот так они работают. Для закрепления этого материала
пройдите практические задания, а затем, на покорение нового материала – к новым
урокам!
Курс по Python: https://stepik.org/course/100707