Перейти к содержимому
Теория · Функции · урок 3 из 8

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

В прошлом уроке функция была кофемашиной с ручками. Теперь посмотрим, где машина стоит и что она видит вокруг.

Машина стоит в кафе. На стене кафе висит доска с объявлениями — её видят все посетители и сама машина тоже. Внутри у машины свой блокнот для расчётов. Туда она записывает промежуточные числа, а снаружи этого блокнота никто не видит.

Когда заказ готов, страницу из блокнота вырывают. Следующий заказ начинается с чистого листа. И главное: если машина запишет в блокнот число под тем же именем, что и на доске, доска от этого не изменится.

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

Python
level = 1


def show_level():
    print(level)


def reset_level():
    level = 0
    print(level)


show_level()  # 1
reset_level()  # 0
print(level)  # 1

Разберём по шагам:

  1. level = 1 пишет на доску: переменная создана вне функций.
  2. show_level() читает level с доски и печатает 1.
  3. reset_level() записывает level = 0 в свой блокнот и печатает 0.
  4. Последний print(level) снова смотрит на доску и печатает 1: её никто не менял.

Локальные и глобальные имена

Переменная, которой присвоили значение вне функций, — глобальная: её видно во всём файле, в том числе внутри функций. Это доска на стене.

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

Часть программы, где имя доступно, называют областью видимости. Локальные переменные появляются при вызове и исчезают, когда функция возвращает результат. Каждый вызов начинает с новыми локальными переменными. Снаружи функции её локальных имён нет:

Python
def square(x):
    result = x * x
    return result


print(square(4))  # 16
print(result)  # ✗
TEXT
NameError: name 'result' is not defined

Функция вернула 16, а её блокнот с result и x уже выброшен. Поэтому две функции могут завести локальные переменные с одним именем, и эти переменные друг от друга не зависят.

Когда функция читает имя, которого нет в её блокноте, Python ищет его дальше. Для функции, объявленной прямо в файле, порядок такой:

  1. локальные переменные вызова;
  2. глобальные переменные файла;
  3. встроенные имена Python, такие как print и abs.

Присваивание делает имя локальным

Здесь аналогия с блокнотом требует уточнения. Машина решает, какие имена писать в блокнот, заранее — ещё до того, как начнёт готовить заказ.

Python решает, какие имена функции локальные, ещё до её вызова, по всему телу. Если имени где-нибудь в теле присваивают значение через = или +=, оно локальное во всей функции. Это касается и строк выше присваивания, и правой части самого присваивания.

Поэтому level = 0 в reset_level создаёт новую локальную level, а глобальная остаётся равной 1. Функция show_level ничего не присваивает имени level. Это имя для неё не локальное, и Python берёт глобальную переменную.

Инструкция global

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

Python
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?

Python
score = 0


def add_point():
    score += 1  # ✗


print("before")  # before
add_point()
TEXT
UnboundLocalError: cannot access local variable 'score' where it is not associated with a value

Из-за += имя score локальное во всей функции. Чтобы прибавить 1, Python читает локальную score, а значения у неё ещё нет. Проверка файла этого не видит, поэтому before печатается, и ошибка возникает на вызове.

Исправить можно двумя способами:

  • добавить global score первой строкой тела;
  • передать счёт в параметре и вернуть новый.

Главное

  • Переменная, созданная вне функций, глобальная; созданная внутри функции и параметры — локальные.
  • Локальные переменные живут только во время вызова, снаружи их нет.
  • Присваивание где угодно в теле делает имя локальным во всей функции.
  • Чтобы изменить глобальную переменную, в начале тела пишут global имя.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом