Зачем вам глобальные переменные global

автор рубрика
Зачем вам глобальные переменные global

Один из приемов, который часто используют в программировании — глобальные переменные. Но в Python глобальные переменные более детального объяснения. В этой статье вы узнаете, что такое global и зачем он нужен в Python.

Области имен (namespace) определяются блоками кода

Рассмотрим следующий кусок кода:

foo = 42

def f():
    global foo
    print(foo)  # печатает 42

f()

Что означает ключевое слово global в функции и что оно делает? Первое, что приходит в голову так это то, что переменная становится видимой изнутри функции. Но реальность куда сложнее.

Python-скрипт состоит блоков кода: тела функции и модулей. Например, следующем пример состоит из 3 блоков:

def f():
    def g():
        print(10)

— один блок модуля, два остальных — блоки функций f и g. Следовательно, программа на Python состоит как минимум из одного блока кода, которым является модуль. Стоит заметить, что отступы после операторов (например, if) не определяют новый блок, в отличие от Си-подобных языков, где используются фигурные скобки.

Область видимости

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

В Python область видимости определяется блоками кода. Когда имя используется в блоке, то оно разрешается (т.е. какое значение будет использоваться) ближайшей областью видимости. Перепишем тот код без использования global:

foo = 42

def f():
    print(foo)  # печатает 42

f()

Так как переменной foo нет в теле функции, её поиски продолжаются путем перехода в блок кода уровнем выше, т.е. модуля. Если переменная с таким именем на этом уровне не находится, то вызывается исключение NameError.

Все дело в связывании в Python

В Си-подобных языках переменные можно объявлять, показав компилятору, что она будет позже использоваться путем присваивания. В Python нет объявлений, но есть связывание (binding), когда значение ассоциируется с именем. Поэтому если нам нужна какая-то переменная, то мы инициализируем её сразу. А вот при присваивании нового значения в той же области видимости происходит повторное связывание (re-binding).

// В СИ:
int foo;
// остальной код
// И где-то уже присваиваем ей значение
foo = 10;

# В Python сразу инициализируется значение:
foo = 10
# повторное связывание:
foo = 20

Операция “присваивание” — не единственная операция, которая производит связывание. Следующие конструкции также связывают имена и значения [1]:

  • описание функции связывает имя функции с самой функцией;
  • при вызове функции имена аргументов связываются с передаваемыми значениями;
  • в цикле for имя в заголовке связывается со значением, генерируемым итератором каждый раз;

В следующей программе сначала напечатается 10, затем 23:

foo = 23

def f():
    foo = 10
    print(foo)  # печатает 10

f()
print(foo)  # печатает 23

Первое присваивание переменной foo происходит на верхнем модульном уровне. Присваивание внутри функции осуществляется в другом блоке (в функции). Несмотря на то, что используются одинаковые имена никакого повторного связывания не происходит, потому что связывания осуществляется в разных блоках.

Другой пример на Python:

def f():
    foo = 42
    def g():
        def h():
            foo = 10
            def i():
                print(foo)  # печатается 10
            i()
        h()
        print(foo)  # печатается 42
    g()

f()

Самый внутренний блок i ищет имя из ближайшей области видимости, которой должна находиться либо в этом блоке, либо выше. Переменные блоком ниже текущего уровня не известны, поэтому второй вывод печатает значение foo из блока f, а не h.

Зачем нужен global в Python

Так зачем же в Python существует ключевое слово global? Давайте снова взглянем на программу без global:

foo = 42

def f():
    foo = 10
    print(foo)  # печатает 10

f()
print(foo)  # печатает 42

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


Эта проблема и объясняет зачем нужен оператор global (за ним может идут разделенные запятой переменные из другого блока). Данный оператор ведет себя как операция связывания, т.е. происходит повторное связывание. C его помощью можно создавать глобальные переменные, которые можно изменять где угодно (но увлекаться не стоит, так как чрезмерное их использование может дорого стоить). Поэтому добавив global перед переменной foo внутри функции вы изменяете её и внутри модуля.

Переменные нужно сначала инициализировать

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

Рассмотри пример:

count = 0

def inc():
    count = count + 1
    print(count) # UnboundLocalError: local variable 'count' referenced before assignment

inc()

Казалось бы, переменная введена, но исключение все равно вызывается. Почему? Поскольку нет оператора global, то это означает, что должно быть произведено новое связывание внутри функции переменной count. Но для начала Python должен вычислить выражение справа от знака “равно”. Но справа стоит переменная count, и она уже привязана к блоку функции, а значит не инициализирована (значение не определено).

Использование nonlocal

Мы видели, что Python предоставляет оператор global, который позволяет повторно связывать имена. Что, если мы хотим повторно связать имя, которое не находится на модульном уровне? В таком случае используйте оператор nonlocal. Тем самым вы можете переназначать переменные вложенных функций:

def f():
    foo = 42
    def g():
        nonlocal foo
        foo = 10
    g()
    print(foo)  # печатается 10

f()

Ещё больше подробностей о Python вы узнаете вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и ИТ-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве:

Источники
  1. https://blog.chiodini.org/posts/python-global/
Комментировать