ПроКодинг - Откроем для вас мир IT!

Вы пишете функцию, хотите изменить переменную снаружи, а Python возвращает ошибку UnboundLocalError. Или хуже - код работает, но значение переменной не меняется так, как вы планировали. Это классическая ловушка областей видимости. Чтобы разобраться, почему это происходит и как исправить, нужно понять два ключевых слова: global и nonlocal.

Эти инструменты управляют тем, где Python ищет переменные и куда сохраняет новые значения. Без них функции были бы изолированными островами, не способными взаимодействовать с внешним миром через общие данные. Но с ними легко создать «спагетти-код», который невозможно отладить.

Правило LEGB: как Python ищет имена

Прежде чем трогать global, нужно знать, как интерпретатор находит переменные по умолчанию. Python использует правило LEGB (Local, Enclosing, Global, Builtins). Это порядок поиска имен при чтении значений:

  • L (Local): Локальная область. Переменные внутри текущей функции.
  • E (Enclosing): Охватывающая область. Переменные во внешней функции (для вложенных функций).
  • G (Global): Глобальная область. Переменные на уровне модуля (в файле).
  • B (Builtins): Встроенная область. Стандартные функции вроде print() или len().

Когда вы просто читаете переменную (x = y + 1), Python идет по этому списку сверху вниз. Но как только вы пытаетесь присвоить значение переменной (y = 10), правила меняются. По умолчанию Python считает, что вы создаете новую локальную переменную. Если вы попробуете прочитать эту переменную до присваивания, возникнет ошибка, потому что для Python она уже «локальная», но еще пустая.

Ключевое слово global: доступ к модулю

global говорит интерпретатору: «Не создавай локальную переменную. Используй переменную из глобальной области видимости (уровня файла)».

Пример использования global
Без global С global
count = 0
def increment():
    count += 1 # Ошибка UnboundLocalError
increment()
count = 0
def increment():
    global count
    count += 1 # Работает корректно
increment()
print(count) # 1

Обратите внимание: объявление global должно быть в начале функции. Оно действует на все последующие обращения к этой переменной внутри функции. Вы можете объявить несколько переменных сразу: global x, y, z.

Важный нюанс: global позволяет создать переменную на уровне модуля, даже если её там раньше не было. Это полезно для ленивой инициализации кэша, но часто считается плохой практикой, так как скрывает побочные эффекты функции.

Ключевое слово nonlocal: работа с замыканиями

nonlocal появилось в Python 3 специально для работы с вложенными функциями. Оно указывает искать переменную не в локальной области, и не в глобальной, а в ближайшей охватывающей (enclosing) области. То есть, во внешней функции, которая содержит текущую.

Это основной инструмент для создания замыканий с изменяемым состоянием.

def counter():
    count = 0 # Эта переменная видна только внутри counter
    
    def increment():
        nonlocal count # Ищем count во внешней функции counter
        count += 1
        return count
    
    return increment

my_counter = counter()
print(my_counter()) # 1
print(my_counter()) # 2

Без nonlocal попытка изменить count вызвала бы ошибку. С global мы бы искали переменную на уровне всего файла, что неверно, если таких счетчиков много.

Абстрактная иллюстрация различий между global и nonlocal

Главное различие: таблица сравнения

Многие новички путают эти ключевые слова. Вот четкие критерии выбора:

Сравнение global и nonlocal
Характеристика global nonlocal
Уровень доступа Модуль (файл) Внешняя функция (замыкание)
Использование вне функций Невозможно Невозможно (только внутри вложенной функции)
Создание новой переменной Да (если её нет) Нет (вызывает SyntaxError, если имя не найдено)
Поиск в цепочке LEGB Переходит сразу к G Ищет в E, игнорируя G и B

Типичные ошибки и как их избежать

Работа с областями видимости полна подводных камней. Вот три самых частые проблемы:

  1. Ошибка UnboundLocalError: Вы пытаетесь изменить глобальную переменную, забыв написать global. Python думает, что вы создаете локальную копию, но пытается прочитать её значение до присваивания. Решение: добавьте global name в начало функции.
  2. Ошибка SyntaxError: no binding for nonlocal 'x' found: Вы используете nonlocal, но переменной с таким именем нет ни в одной внешней функции. nonlocal не смотрит в глобальную область. Если переменная глобальная, используйте global. Если переменной нет вообще, создайте её во внешней функции.
  3. Неправильный порядок объявления: Объявление global или nonlocal стоит после первого использования переменной. Интерпретатор анализирует функцию целиком перед запуском. Если он видит присваивание без декларации, он помечает переменную как локальную. Декларация должна быть первой строкой использования этого имени.
Метафора перехода от глобальных переменных к классам в коде

Антипаттерны: когда лучше не использовать

Несмотря на полезность, опытные разработчики стараются избегать global и аккуратно относятся к nonlocal. Почему?

Global нарушает чистоту функций. Функция, использующая глобальные переменные, зависит от внешнего состояния. Её сложнее тестировать, так как нужно очищать глобальное окружение между тестами. Она менее переносима: вы не можете просто скопировать такую функцию в другой проект, не взяв с собой весь контекст модуля.

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

Лучшие практики: альтернативы

Как писать код без этих ключевых слов? Есть несколько проверенных подходов:

  • Используйте параметры и return. Вместо изменения переменной, передавайте её в функцию и возвращайте новое значение. Это делает поток данных явным.
    def increment(count):
        return count + 1
    
    count = 0
    count = increment(count)
  • Используйте классы. Если вам нужно хранить состояние, создайте класс. Атрибуты объекта - это легальный способ хранения изменяемых данных.
    class Counter:
        def __init__(self):
            self.count = 0
        def increment(self):
            self.count += 1
            return self.count
  • Используйте изменяемые объекты. Списки и словари можно изменять внутри функции без global или nonlocal, потому что вы не переприсваиваете саму переменную, а меняете содержимое объекта.
    data = []
    def add_item(item):
        data.append(item) # Работает без global
    add_item(1)

FAQ

Можно ли использовать global и nonlocal вместе?

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

Работает ли nonlocal с глобальными переменными?

Нет. Nonlocal ищет переменные только в охватывающих функциях (Enclosing scope). Если переменная определена на уровне модуля, nonlocal её не найдет и вызовет ошибку. Для глобальных переменных всегда используйте global.

Нужно ли использовать global для чтения переменных?

Нет. Global нужен только для присваивания (изменения) значений. Для чтения Python автоматически найдет переменную в глобальной области видимости, если не найдет её в локальной.

Почему возникает ошибка UnboundLocalError?

Она возникает, когда вы пытаетесь прочитать переменную внутри функции до того, как ей присвоили значение, но при этом в функции есть оператор присваивания для этой переменной. Python определяет переменную как локальную со старта выполнения функции, поэтому чтение до инициализации невозможно.

Какой современный подход вместо global?

Предпочтительнее использовать классы для инкапсуляции состояния или передавать данные через аргументы функции и возвращать результаты. Это делает код более предсказуемым и тестируемым.