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

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

Локальные переменные

Для создания переменных, обладающих локальной областью видимости, необходимо всего лишь поместить их в отдельный блок кода, изолированный от остальной программы. Чтобы увидеть локальную переменную в действии, достаточно инициализировать целочисленный объект с именем x и значением 100 в функции f, как это сделано в следующем примере:

def f():
    x = 100
    print(x)
f()

100

Здесь x имеет локальную область видимости, так как доступна лишь в рамках своей функции f. Вызывая данную функцию из внешней части программы, можно увидеть вывод целочисленного значения на экране. Однако, если попытаться вывести переменную x при помощи метода print вне зоны действия функции f, компилятор тут же выдаст ошибку:

def f():
    x = 100
    print(x)
f()
print(x)

100
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    print(x)
NameError: name 'x' is not defined

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

Глобальные переменные

Чтобы иметь возможность использовать некоторое значение в любой части программы, следует объявить глобальную переменную. Для этого понадобиться создать переменную отдельно от области кода, ограниченной определенным блоком кода, например, функцией. В следующем примере демонстрируется идентификация целочисленного типа данных под названием x, который позже выводится на экран при помощи метода print в функции f:

x = 100
def f():
    print(x)
f()
print(x)

100
100

Как можно заметить из результатов выполнения программы, значение 100 воспроизводится не только через f, но и с помощью обычного print. Таким образом, получение доступа к x осуществляется из любой части кода, благодаря глобальной области видимости подобного объекта. Но что будет, если попытаться изменить значение глобальной переменной в некой функции? Результаты такого эксперимента представлены в следующем фрагменте кода:

x = 100
def f():
    x = 200
f()
print(x)

100

Функция f присваивает значение 200 переменной с именем x, однако, вопреки ожиданиям, внешний метод print выводит число 100, которое принадлежало x изначально. Происходит так потому, что в данной программе создаются два разных объекта x с локальной, а также глобальной областью видимости. Исправить ситуацию поможет ключевое слово global:

x = 100
def f():
    global x
    x = 200
f()
print(x)

200

Пометив переменную x как global, можно обращаться к ее изначальному значению, которое было определено вне зоны действия функции f. Теперь после того как в x поместили число 200, вызов метода print выводит вполне ожидаемый результат, то есть измененное значение.

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

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

def my_abs(val):
    return -val if val < 0 else val
    return val + 1
x = -15
x = my_abs(x)

Нелокальные переменные

Итак, для обращения к глобальной переменной внутри функции f необходимо использовать ключевое слово global перед ее идентификатором. Но что если требуется вызывать совсем не глобальную, а переменную, которая была определена во внешнем методе, являясь при этом локальной для другого пространства имен, находящегося на уровень выше? Следующий код демонстрирует попытку взаимодействия со значением из внешней функции f1 в методе f2:

def f1():
    x = 100
    def f2():
        x = 200
    f2()
    print(x)
f1()

100

Несмотря на то, что переменной с таким же именем x было присвоено новое значение 200, в результате выполнения написанных методов на экране отобразилось 100. Как и в том случае с двумя разными переменными, локальной и глобальной, здесь имеется также два различных объекта, которые идентифицированы в отдельных блоках кода. Чтобы обратиться к объекту, который не является локальным, необходимо воспользоваться модификатором nonlocal:

def f1():
    x = 100
    def f2():
        nonlocal x
        x = 200
    f2()
    print(x)
f1()

200

Таким образом, в методе f2 осуществляется запись значения 200 в переменную x из функции f1. В результате подобных действий, вызов метода f1 из внешней части программы создает новую переменную x, значение которой меняется в f2 со 100 на 200 и выводится при помощи print.

Важно заметить, что конструкция nonlocal была добавлена только в 3-ей версии языка Python.

Видимость из загружаемого модуля

Теперь разберемся с видимостью глобальных переменных между загружаемыми модулями Python. Например, мы подключаем другой модуль с помощью команды import. Создадим файл “test.py” и в него запишем следующий код:

print('Загружается модуль test')
x = 100
def f():
    print('Из функции x=' + str(x))

То есть мы определили глобальную переменную x для модуля test. Так же определили функцию, которая выводит на экран её значение.

Теперь создадим файл main.py, который и будем запускать. В нем мы импортируем модуль test, а так же создадим свою глобальную переменную x. После этого выведем значения глобальной переменной из test, вызовим функцию f, а так же проверим, что значение переменной в модуле main не изменилось:

x = 200
print('Из модуля main x=' + str(x))
import test
print('Из модуля test x=' + str(test.x))
print('Присваиваем значение 300')
test.x = 300;
print('Из модуля test x=' + str(test.x))
test.f()
print('Из модуля main x=' + str(x))

Из модуля main x=200
Загружается модуль test
Из модуля test x=100
Присваиваем значение 300
Из модуля test x=300
Из функции x=300
Из модуля main x=200

Мы в первой же строчке записали в x значение 200. Это было сделано, чтобы показать, что после того, как мы загрузим внешний модуль, значение этой переменной не изменится. Так и вышло. Обращаясь к переменной из загруженной библиотеки, удалось прочитать его и изменить значение.

Теперь модифицируем программу следующим образом:

x = 200
print('Из модуля main x=' + str(x))
from test import *
f()
print('Из модуля main x=' + str(x))
print('Присваиваем значение 300')
x = 300
f()
print('Из модуля main x=' + str(x))

Из модуля main x=200
Загружается модуль test
Из функции x=100
Из модуля main x=100
Присваиваем значение 300
Из функции x=100
Из модуля main x=300

В этом случае для загрузки мы использовали команду “from test import *”. Мы импортировали все переменные и функции. После загрузки модуля значение переменной x в модуле main изменилось. Но при вызове функции, мы получаем значение x из модуля test. После присвоения нового значения переменной x, значение, которое выводит функция f не изменяется.

Следует по возможности избегать подключение библиотек с помощью команды from библиотека import *, а подключать только необходимые функции — from библиотека import функция. При этом надо удостовериться, что эти имена не используются в основном модуле.
Использование import библиотека поможет избежать возможных ошибок, если в программе есть функции, классы и переменные с такими же наименованиями, как и в загружаемом модуле.

Глобальные переменные в классе

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

x = 100
print(x)
class c1:
    global x
    x = 200
    def __init__(self):
        global x
        x = 300
    def f(self):
        global x
        x = 400
print(x)
o1 = c1()
print(x)
o1.f()
print(x)

100
200
300
400

Мы объявили глобальную переменную x. Вывели значение переменной до и после объявления класса. Как видим значение изменилось. После того как мы создали объект класса, значение в очередной раз поменялось. Это произошло, потому что сработал конструктор класса – метод __init__. После вызова функции f у созданного объекта, значение стало 400. В Python использование global переменная и в функции класса, и в его конструкторе, и после описания класса дают возможность изменения глобальной переменной. Если убрать это объявление, то тогда выполнится присвоение локальной переменной.

Конечно же если мы определим локальную переменную в классе, то к ней не будет возможности доступа из другого класса:

class c1:
    x = 100
class c2:
    def f(self):
        print(x)
o = c2()
o.f()

Traceback (most recent call last):
  File "main.py", line 7, in <module> 
    o.f()
  File "main.py", line 5, in f
    print(x)
NameError: name 'x' is not defined

Для того, чтобы код работал, переменная x должна быль глобальной.

Заключение

Таким образом, область видимости переменных в языке программирования Python, является важной составляющей платформы. Правильное взаимодействие со всеми ее особенностями позволяет избежать множества довольно сложных ошибок. Для более безопасного контроля над видимостью отдельных объектов применяются ключевые слова global и nonlocal. Чтобы ознакомиться с дополнительными сведениями по данной теме, следует изучить PEP 3104.