Почему некорректное присвоение глобальной переменной вызывает исключение раньше?
a = 10
def f():
print(1)
print(a) # UnboundLocalError raised here
a = 20
f()
Этот код, конечно, поднимает UnboundLocalError: local variable 'a' referenced before assignment
. Но почему это исключение возникает в строке print(a)
?
Если интерпретатор выполнил код по строкам (как я думал, он это сделал), он не знал бы, что что-то было не так, когда print(a)
было достигнуто; он просто подумал бы, что a
ссылается на глобальную переменную.
Итак, кажется, что интерпретатор заранее читает всю функцию, чтобы выяснить, используется ли a
для назначения. Является ли это документированным где угодно? Есть ли другой случай, когда интерпретатор смотрит вперед (кроме проверки на синтаксические ошибки)?
Чтобы прояснить, само исключение совершенно ясно: глобальные переменные могут быть прочитаны без объявления global
, но не написаны (этот проект предотвращает ошибки из-за непреднамеренно изменяющихся глобальных переменных; эти ошибки особенно сложно отлаживать, поскольку они приводят к ошибки, которые происходят далеко от местоположения ошибочного кода). Мне просто интересно, почему исключение поднимается раньше.
Ответы
Ответ 1
Согласно документации Python, интерпретатор сначала заметит назначение переменной a
в области f()
( независимо от положения назначения в функции), а затем, как следствие, распознать переменную a
как локальную переменную в этой области. Это поведение эффективно shadows глобальная переменная a
.
Затем исключение возникает "раньше", потому что интерпретатор, выполняющий код "строка за строкой", столкнется с оператором печати, ссылающимся на локальную переменную, которая еще не связана в этот момент (помните, что Python ищет a local
здесь).
Как вы упомянули в своем вопросе, нужно использовать ключевое слово global
, чтобы явным образом сообщить компилятору, что присвоение в этой области выполняется для глобальной переменной, правильный код:
a = 10
def f():
global a
print(1)
print(a) # Prints 10 as expected
a = 20
f()
Как @2rs2ts говорится в теперь удаленном ответе, это легко объяснить тем, что "Python не просто интерпретируется, он скомпилирован в байт-код и не просто интерпретируется по строкам".
Ответ 2
В разделе Resolution of Names справочного руководства Python сказано:
[..] Если текущая область действия является областью действия, а имя относится к локальной переменной, которая еще не привязана к значению в точке, где используется имя, возникает исключение UnboundLocalError
[..]
что официальное слово, когда происходит UnboundLocalError
. Если вы посмотрите на байт-код, который создает CPython для вашей функции f
с помощью dis
, вы можете увидеть его, пытаясь загрузить имя из локальная область, когда ее значение еще не установлено:
>>> dis.dis(f)
3 0 LOAD_GLOBAL 0 (print)
3 LOAD_CONST 1 (1)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
4 10 LOAD_GLOBAL 0 (print)
13 LOAD_FAST 0 (a) # <-- this command
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
19 POP_TOP
5 20 LOAD_CONST 2 (20)
23 STORE_FAST 0 (a)
26 LOAD_CONST 0 (None)
29 RETURN_VALUE
Как вы можете видеть, имя 'a'
загружается в стек с помощью команды LOAD_FAST
:
13 LOAD_FAST 0 (a)
Это команда, которая используется для захвата локальных переменных в функции (с именем FAST
из-за того, что она выполняется быстрее, чем загрузка из глобальной области с помощью LOAD_GLOBAL
).
Это действительно не имеет ничего общего с глобальным именем a
, которое было определено ранее. Это связано с тем, что CPython предположит, что вы играете красиво и генерируете LOAD_FAST
для ссылок на 'a'
, поскольку 'a'
назначается (т.е. Создается локальное имя) внутри тела функции.
Для функции с единственным именем доступа и без соответствующего назначения CPython не генерирует LOAD_FAST
и вместо этого обращается к глобальной области с помощью LOAD_GLOBAL
:
>>> def g():
... print(b)
>>> dis.dis(g)
2 0 LOAD_GLOBAL 0 (print)
3 LOAD_GLOBAL 1 (b)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
Итак, кажется, что интерпретатор заранее читает всю функцию, чтобы выяснить, используется ли a
для назначения. Является ли это документированным где угодно? Есть ли другой случай, когда интерпретатор смотрит вперед (кроме проверки на синтаксические ошибки)?
В разделе "Составные выражения" справочного руководства для определения функций указано следующее:
Определение функции - исполняемый оператор. Его выполнение связывает имя функции в текущем локальном пространстве имен с объектом функции (оболочкой вокруг исполняемого кода для функции).
В частности, он связывает имя f
с функциональным объектом, который содержит скомпилированный код f.__code__
, который dis
превалирует для нас.