Замыкания (closures) — мощный механизм Python, позволяющий создавать функции с "запомненными" данными из окружающего контекста.
В функциональных языках и в Python функции — объекты первого класса. Функция может вернуть новую функцию, которая 'замыкает' переменные из своей окружающей (внешней) области видимости.
Разработчики часто путаются, как именно замкнутые переменные "живут" внутри closure, когда они читаются или записываются, как с ними работать безопасно (особенно с изменяемыми объектами).
Если внутренняя функция использует переменные внешней функции, они автоматически "запоминаются" — даже если внешняя функция уже завершила работу. Для чтения переменной всё очевидно, но если нужно поменять значение — требуется использовать ключевое слово nonlocal. При работе с изменяемыми объектами (lists, dicts) это особая зона риска.
Пример:
def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2
Ключевые особенности:
nonlocal (иначе операция создает локальную переменную).Можно ли изменять значение замкнутой переменной внутри функции без nonlocal?
Нет. Если попытаться присвоить новое значение, не указав nonlocal, Python воспринимает это как создание новой локальной переменной, и старое значение наружу не уйдет.
Пример:
def make_counter(): count = 0 def inner(): count += 1 # Ошибка UnboundLocalError! return count return inner
Можно ли передать аргументы во внешний scope через closure?
Да, closure "запомнит" любые переменные, доступные во внешней области видимости, включая атрибуты классов, глобальные переменные и т.п. Но изменение этих переменных требует особых усилий (например, использование nonlocal или global).
Что происходит с изменяемыми объектами внутри closure?
Если замкнута переменная-ссылка на изменяемый объект, например, список, вы можете модифицировать его содержимое без nonlocal, но если вы попробуете переприсвоить переменную — потребуется nonlocal.
Пример:
def make_appender(): result = [] def append(x): result.append(x) # Можно! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]
Разработчик пишет closure, изменяет переменную без nonlocal — возникает UnboundLocalError.
Плюсы:
Минусы:
Явное использование nonlocal для управляемого состояния в closure.
Плюсы:
Минусы: