Области видимости; инструкции global, nonlocal

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

Например, в приведённом ниже коде:

def add(x, y):
    return x + y

a = 1
b = 2
print(add(a, b))

Где существуют x и y? Ответ: только внутри функции add. Более того, при каждом вызове функции add создаётся новая область, и в каждой такой области свои x и y.

Всего в Python четыре области видимости:

  1. Локальная - это внутри функции (обычной или lambda)

  2. Охватывающая (nonlocal) - особая область видимости, которая существует только для вложенных функций. Если локальная область видимости - это внутренняя функция, то объемлющая область видимости - это область видимости внешней функции. Например:

    def add(a, b, c, d):
        def add_2(x, y);
            return x + y
        return add_2(a, b) + add_2(c, d)
    

    для функции add_2 переменные x, y находятся в локальной области видимости, а переменные a, b, c, d - в объемлющей.

  3. Глобальная - эта область видимости Python содержит все имена переменных, которые вы определяете на верхнем уровне программы или модуля. Имена в этой области видимы отовсюду в вашем коде.

  4. Встроенная область видимости - это специальная область видимости Python, которая создается при запуске программы. Эта область содержит такие имена, как ключевые слова, встроенные функции, исключения и другие атрибуты, которые встроены в Python. Имена в этой области видимости Python также доступны отовсюду в вашем коде.

Когда мы пишем variable = "some value" внутри функции, то переменная variable будет находиться в области видимости этой функции (а также вложенных функций).

Однако, когда мы пишем внутри функции do_something(variable), то variable ищется во всех областях видимости, по порядку от первой до четвёртой.

В этих двух пунктах может случиться коллизия. Например, мы напишем

counter = 0
def increment_counter():
    counter = counter + 1
    return counter

print(increment_counter())

counter = counter + 1 до присваивания будет искать переменную counter во всех областях видимости, и найдёт её в глобальной области; а во время присваивания вроде как нужно создать в локальной области.

Python без специальных инструкций так делать не позволяет:

    counter = counter + 1
              ^^^^^^^
UnboundLocalError: cannot access local variable 'counter' where it is not associated with a value

Для разрешения таких случаев есть специальные инструкции:

  • global говорит интерпретатору, что переменную необходимо создавать и искать в глобальной области видимости
  • nonlocal говорит, что переменную необходимо создавать и искать в нелокальной области видимости

Код выше можно переписать так:

counter = 0
def increment_counter():
    global counter
    counter = counter + 1
    return counter

print(increment_counter())