Разбор ошибки: определите результат вызова show() и объясните, какое правило областей видимости срабатывает.
value = 10
def show():
print(value)
value = 20
show()
Вызов show() завершится исключением UnboundLocalError, потому что value считается локальной переменной функции. Присваивание value = 20 заставляет Python трактовать все обращения к value внутри show() как обращения к локальному имени, включая обращение до присваивания.
Python использует лексические области видимости: имя ищется в локальной области, затем во внешних областях функций, глобальной области модуля и встроенной области. Такой подход делает разрешение имён предсказуемым по структуре исходного кода, а не зависящим от стека вызовов.
Для этого компилятор анализирует присваивания внутри функции заранее. Поэтому область видимости имени определяется не порядком выполнения строк, а наличием операции связывания имени в теле функции.
В примере разработчик ожидает, что print(value) прочитает глобальное значение 10, а следующая строка изменит его. Однако наличие присваивания превращает value в локальное имя всей функции.
До выполнения value = 20 локальная переменная ещё не получила значение. Чтение такого имени вызывает UnboundLocalError, а не поиск значения в глобальной области. Неверное понимание этого правила приводит к ошибкам при добавлении счётчиков, изменении настроек и рефакторинге функций.
При компиляции функции Python видит присваивание value = 20 и классифицирует value как локальную переменную. Во время выполнения инструкция print(value) пытается прочитать локальную переменную до её инициализации, поэтому возникает UnboundLocalError.
Если требуется только прочитать глобальное имя, присваивание внутри функции нужно убрать:
Если функция действительно должна изменить глобальную переменную, используется явное объявление global:
global меняет область связывания имени, но не создаёт копию значения. Такой код может ухудшить тестируемость и скрыть зависимости функции, поэтому обычно предпочтительнее передавать значение аргументом и возвращать новый результат.
Важно отличать UnboundLocalError от NameError. UnboundLocalError — специализированный случай ошибки доступа к локальному имени, которому ещё не присвоено значение. Если имя вообще не найдено ни в одной доступной области, возникает NameError.
В обработчике запросов хранился глобальный лимит:
Вариант с global исправляет ошибку, но создаёт общее изменяемое состояние: поведение зависит от порядка вызовов, усложняется параллельное выполнение и тестирование. Передача состояния через объект или аргумент безопаснее с точки зрения структуры программы, но требует немного больше кода.
Практическим решением будет объект с явно принадлежащим ему состоянием:
Так зависимость становится явной, разные экземпляры не конфликтуют, а тесты могут создавать независимые объекты. global оправдан только для действительно модульного состояния, изменение которого осознанно является частью дизайна.
Ответ: при show(False) возникнет UnboundLocalError. Для компилятора достаточно самого факта присваивания в теле функции: value локальна независимо от того, выполнится ли ветка. Условие влияет на инициализацию во время выполнения, но не на классификацию имени.
value += 1 вызывает ту же проблему, что и value = value + 1?Ответ: составное присваивание включает чтение старого значения и последующую запись нового. Если имя локальное, его чтение происходит до локальной инициализации, поэтому возникает UnboundLocalError. Оператор += не превращает глобальное имя в изменяемое без объявления global.
Ответ: используется объявление nonlocal, которое связывает имя с ближайшей внешней функциональной областью:
Здесь value не является глобальной: вложенная функция изменяет переменную из области counter. nonlocal требует существующего имени во внешней функции; для глобальной области применяется global.