Вложенная функция должна переназначить имя из внешней функции: каким механизмом Python нужно явно воспользо...

Вложенная функция должна переназначить имя из внешней функции: каким механизмом Python нужно явно воспользоваться?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Для переназначения имени из непосредственно внешней функции используют nonlocal. Он сообщает Python, что присваивание относится не к локальной переменной вложенной функции, а к существующей переменной во внешней функциональной области видимости.

Исторический контекст

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

Ключевое слово nonlocal было добавлено в Python 3. Оно отделяет изменение состояния замыкания от изменения глобального состояния и делает намерение разработчика явным.

Постановка проблемы

Если вложенная функция только читает имя из внешней функции, специальное объявление не требуется. Но при присваивании Python заранее классифицирует это имя как локальное для вложенной функции, поэтому попытка прочитать его до присваивания может привести к UnboundLocalError, а внешнее имя не изменится.

Неверный выбор области видимости приводит к трудно заметным ошибкам: счётчик замыкания не увеличивается, состояние сбрасывается на каждом вызове или случайно изменяется глобальная переменная.

Подробное решение

Объявление nonlocal изменяет правило разрешения имени только для текущей вложенной функции. При присваивании Python ищет имя в ближайшей содержащей функциональной области видимости и изменяет привязку там.

def make_counter(): count = 0 def next_value(): nonlocal count count += 1 return count return next_value counter = make_counter() print(counter()) print(counter())

Оба вызова возвращают последовательно 1 и 2, потому что переменная count живёт в замыкании внешней функции, а nonlocal разрешает вложенной функции переназначать эту привязку.

nonlocal не обращается к глобальной области видимости. Для глобального имени используется global, однако глобальное состояние обычно ухудшает тестируемость и создаёт неявные зависимости.

Имя, указанное после nonlocal, должно существовать в некоторой внешней функциональной области видимости. Нельзя объявить через nonlocal имя, существующее только на уровне модуля, и нельзя использовать это ключевое слово для изменения объекта: оно управляет переназначением имени, а не напрямую мутацией объекта.

Если внешнее имя связано с изменяемым объектом, его содержимое можно изменять без nonlocal. Но если требуется присвоить имени новый объект, nonlocal необходим.

Ситуация из практики

В фабрике обработчиков нужно хранить число успешно обработанных событий между вызовами. Рассматривались три варианта: глобальный счётчик, объект с атрибутом и замыкание с nonlocal.

Глобальная переменная проще, но создаёт проблемы при параллельном использовании и тестировании. Отдельный класс лучше масштабируется и позволяет хранить несколько связанных состояний, но добавляет шаблонный код. Замыкание компактно изолирует состояние каждого созданного обработчика и подходит, пока логика состояния невелика.

Для небольшого обработчика выбрано замыкание с nonlocal. Каждый вызов фабрики получает собственную переменную состояния, а код явно показывает, что вложенная функция изменяет именно её. При усложнении состояния переход на класс будет более поддерживаемым решением.

Что кандидаты часто упускают

  1. Чем nonlocal отличается от global?

Ответ: nonlocal ищет имя во внешних функциональных областях видимости, начиная с ближайшей, но не рассматривает глобальную область модуля. global связывает имя с областью модуля. Поэтому nonlocal изолирует состояние конкретного замыкания, а global делает его общим для всего модуля.

  1. Нужно ли nonlocal, если вложенная функция изменяет содержимое списка из внешней функции?

Ответ: нет, если изменяется сам объект, а не привязка имени. Например, добавление элемента в список меняет объект, на который уже ссылается внешнее имя. nonlocal потребуется при присваивании этому имени нового списка или другого объекта.

  1. Что произойдёт, если имя после nonlocal не определено во внешней функции?

Ответ: программа завершится ошибкой на этапе компиляции функции, обычно с исключением SyntaxError. Python требует, чтобы объявление nonlocal ссылалось на существующую привязку во внешней функциональной области видимости; имя из глобальной области для этой цели не подходит.