Что такое замыкание в Python
Что проверяют
Знание модели областей видимости и того, что Python захватывает переменную, а не значение. Вопрос базовый, поэтому ответ «функция внутри функции» не проходит: ждут механизм, отличие от обычной вложенной функции и понимание времени жизни захваченных объектов.
Как отвечать
Замыкание это функция со свободными переменными, связанными с областью объемлющей функции, причём связь переживает возврат из неё. Технически компилятор помечает такие переменные как ячейки, интерпретатор складывает их в кортеж, доступный через атрибут __closure__, а имена лежат в co_freevars объекта кода. Если вложенная функция ничего не берёт снаружи, замыкания нет и атрибут равен None.
def counter(start=0):
n = start
def inc(step=1):
nonlocal n
n += step
return n
return incБез nonlocal присваивание сделало бы n локальной для inc и вызвало UnboundLocalError; для чтения объявление не нужно. global указывает на модуль, nonlocal на ближайшую объемлющую функцию.
Применения: декораторы, в том числе с параметрами, фабрики функций, счётчики и кеш без класса, частичное применение аргументов. Выбор между замыканием и классом простой: одно поведение и немного состояния это замыкание, несколько операций над общим состоянием это класс.
Где спотыкаются
Ловятся на позднее связывание: выражение [lambda: i for i in range(3)] даёт три функции, каждая из которых вернёт 2. Объясняют это «передачей по ссылке», хотя причина в том, что все три делят одну ячейку, а лечится фиксацией значения в параметре по умолчанию или через functools.partial.
Называют замыканием любую вложенную функцию. Если внешние переменные не используются, это просто локально объявленная функция, и никакого хранения окружения за ней нет.
Забывают про удержание объектов. Замыкание держит захваченное столько же, сколько живёт сама функция, поэтому кеш на замыкании растёт до конца жизни модуля, а декоратор без functools.wraps вдобавок стирает имя и документацию обёрнутой функции, ломая интроспекцию и отчёты.