Предполагается, что вы уже умеете писать простые программы на Python и знаете базовый синтаксис (if, for, функции, списки). Здесь мы разбираем не как пользоваться языком, а почему он работает именно так.
Посмотрите на этот код:
x = 10
def show():
x = 20
print(x)
show()
print(x)
Что он выведет?
20
10
Но постойте.
Как такое вообще возможно?
В программе есть переменная x.
Сначала мы присвоили ей 10, а потом внутри функции присвоили ей 20.
Если x - это имя, связанное с объектом, почему после вызова функции оно снова указывает на 10?
Неужели Python каким-то образом вернул старое значение?
Нет.
Никакого возврата не произошло.
На самом деле в этой программе существуют два разных имени x.
В предыдущей статье мы познакомились с пространствами имён.
Имя не существует само по себе.
Оно находится внутри некоторого пространства имён и связывается там с объектом.
Это означает, что одинаковые имена могут существовать одновременно, если они находятся в разных пространствах имён.
Именно это и происходит в нашем примере.
До вызова функции существует пространство имён, в котором есть:
x ───► 10
Когда Python начинает выполнять функцию show(), у неё появляется своё пространство имён.
Внутри него выполняется:
x = 20
Теперь там появляется другая связь:
x ───► 20
Получается примерно такая картина:
Внешнее пространство имён:
x ───► 10
Пространство имён функции:
x ───► 20
Имена называются одинаково.
Но это разные записи.
Поэтому во время выполнения функции:
print(x)
Python находит x, связанное с объектом 20.
После завершения функции её пространство имён больше не используется как текущее.
Мы возвращаемся во внешнее пространство имён.
Там x по-прежнему связано с 10.
Поэтому:
print(x)
выводит:
10
Никакого изменения и последующего восстановления не было.
Просто всё это время существовали два разных имени x.
Это очень важный момент.
Когда мы говорим:
“Внутри функции переменная
xимеет значение20.”
Это немного упрощённая формулировка.
Точнее было бы сказать:
“В пространстве имён функции имя
xсвязано с объектом20.”
А во внешнем пространстве имён имя x в это же время связано с другим объектом:
x ───► 10
Именно поэтому эти два x не конфликтуют друг с другом.
Попробуем немного изменить программу.
x = 10
def show():
print(x)
show()
На этот раз внутри функции мы не создаём собственного x.
Но print(x) всё равно работает.
Python выводит:
10
Откуда функция взяла это имя?
Ведь в её собственном пространстве имён x нет.
Значит, Python должен искать имя где-то ещё.
И здесь мы подходим к одному из самых важных правил Python.
Если имя не найдено в текущем пространстве имён, Python может продолжить поиск в другом.
А теперь попробуем сделать ситуацию ещё интереснее.
x = 10
def show():
x = 20
print(x)
show()
print(x)
Здесь Python нашёл x внутри функции и остановился.
А теперь:
x = 10
def show():
print(x)
show()
Здесь x внутри функции нет.
Поэтому Python продолжает поиск во внешнем пространстве имён.
Получается, что функция не просто “знает” все переменные программы.
Она ищет имя по определённым правилам.
Это объясняет ещё одну довольно распространённую ошибку.
Посмотрите:
x = 10
def show():
print(x)
x = 20
show()
На первый взгляд может показаться, что программа сначала должна вывести 10, а потом присвоить 20.
Но она завершится ошибкой.
Причина в том, что Python рассматривает x внутри функции как локальное имя.
Строка:
x = 20
создаёт связь имени x в пространстве имён функции.
Поэтому print(x) пытается обратиться именно к этому локальному x.
Но в этот момент оно ещё не связано с объектом.
И возникает ошибка:
UnboundLocalError
Это может показаться странным, пока мы думаем о переменных как о “коробках”.
Но если смотреть на всё через пространства имён, поведение становится гораздо понятнее.
Python не говорит:
“Я вижу глобальную переменную
x, поэтому использую её.”
Он сначала определяет, какое имя x имеется в виду в данном месте программы.
И здесь возникает естественный вопрос.
Если Python может искать имя в нескольких пространствах имён, то в каком порядке он это делает?
Почему иногда он берёт локальную переменную?
Почему потом ищет внешнюю?
Что происходит с глобальными переменными?
И что вообще означает загадочная последовательность букв LEGB, которую часто предлагают просто запомнить?
В следующей статье мы разберём это правило без зубрёжки - просто проследим за тем, как Python ищет имя.