Область видимости
В прошлом уроке функция была кофемашиной с ручками. Теперь посмотрим, где машина стоит и что она видит вокруг.
Машина стоит в кафе. На стене кафе висит доска с объявлениями — её видят все посетители и сама машина тоже. Внутри у машины свой блокнот для расчётов. Туда она записывает промежуточные числа, а снаружи этого блокнота никто не видит.
Когда заказ готов, страницу из блокнота вырывают. Следующий заказ начинается с чистого листа. И главное: если машина запишет в блокнот число под тем же именем, что и на доске, доска от этого не изменится.
Давайте посмотрим, как это выглядит в коде. Функция может прочитать переменную, созданную вне неё. Но присваивание той же переменной внутри функции снаружи ничего не меняет:
level = 1
def show_level():
print(level)
def reset_level():
level = 0
print(level)
show_level() # 1
reset_level() # 0
print(level) # 1Разберём по шагам:
level = 1пишет на доску: переменная создана вне функций.show_level()читаетlevelс доски и печатает1.reset_level()записываетlevel = 0в свой блокнот и печатает0.- Последний
print(level)снова смотрит на доску и печатает1: её никто не менял.
Локальные и глобальные имена
Переменная, которой присвоили значение вне функций, — глобальная: её видно во всём файле, в том числе внутри функций. Это доска на стене.
Переменная, которой присвоили значение внутри функции, — локальная. Это запись в блокноте. Параметры тоже локальные: значения аргументов попадают прямо в блокнот вызова.
Часть программы, где имя доступно, называют областью видимости. Локальные переменные появляются при вызове и исчезают, когда функция возвращает результат. Каждый вызов начинает с новыми локальными переменными. Снаружи функции её локальных имён нет:
def square(x):
result = x * x
return result
print(square(4)) # 16
print(result) # ✗NameError: name 'result' is not definedФункция вернула 16, а её блокнот с result и x уже выброшен. Поэтому две функции могут завести локальные переменные с одним именем, и эти переменные друг от друга не зависят.
Когда функция читает имя, которого нет в её блокноте, Python ищет его дальше. Для функции, объявленной прямо в файле, порядок такой:
- локальные переменные вызова;
- глобальные переменные файла;
- встроенные имена Python, такие как
printиabs.
Присваивание делает имя локальным
Здесь аналогия с блокнотом требует уточнения. Машина решает, какие имена писать в блокнот, заранее — ещё до того, как начнёт готовить заказ.
Python решает, какие имена функции локальные, ещё до её вызова, по всему телу. Если имени где-нибудь в теле присваивают значение через = или +=, оно локальное во всей функции. Это касается и строк выше присваивания, и правой части самого присваивания.
Поэтому level = 0 в reset_level создаёт новую локальную level, а глобальная остаётся равной 1. Функция show_level ничего не присваивает имени level. Это имя для неё не локальное, и Python берёт глобальную переменную.
Инструкция global
Чтобы функция изменила глобальную переменную, в начале её тела пишут global и имя. Это как разрешить машине писать прямо на доске. После этого присваивание меняет глобальную переменную, а не создаёт локальную:
calls = 0
def count_call():
global calls
calls += 1
count_call()
count_call()
print(calls) # 2Каждый вызов count_call() прибавляет единицу к глобальной calls, поэтому программа печатает 2.
Для чтения global не нужен: читать глобальную переменную функция может и без него. Функцию, которая меняет глобальные переменные, труднее понять: её результат зависит не только от аргументов. Чаще значение передают в параметре и возвращают новое через return: calls = add_one(calls).
Частая ошибка
Этот вопрос часто задают на собеседовании: что будет, если увеличить глобальную переменную внутри функции без global?
score = 0
def add_point():
score += 1 # ✗
print("before") # before
add_point()UnboundLocalError: cannot access local variable 'score' where it is not associated with a valueИз-за += имя score локальное во всей функции. Чтобы прибавить 1, Python читает локальную score, а значения у неё ещё нет. Проверка файла этого не видит, поэтому before печатается, и ошибка возникает на вызове.
Исправить можно двумя способами:
- добавить
global scoreпервой строкой тела; - передать счёт в параметре и вернуть новый.
Главное
- Переменная, созданная вне функций, глобальная; созданная внутри функции и параметры — локальные.
- Локальные переменные живут только во время вызова, снаружи их нет.
- Присваивание где угодно в теле делает имя локальным во всей функции.
- Чтобы изменить глобальную переменную, в начале тела пишут
global имя.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.