Компиляция против интерпретации, Python имеет неожиданное поведение
Я получаю неожиданное поведение выполнения Python.
if True:
print("Hello")
else:
I am an error. what can you do about it?
Теперь этот код не вызывает SyntaxError, потому что элемент управления никогда не входит в оператор else. В скомпилированных языках, таких как C++, возникает ошибка. Даже в Java неиспользуемый код является ошибкой. Но не в Python.
Теперь в этом случае:
x = 10
def foo():
print(x)
x += 1
Оператор print вызывает UnboundLocalError, как указано здесь. Согласно предыдущей логике, этой ошибки не должно быть, пока элемент управления не встретит x+=1. Но это так, как и в любом другом скомпилированном языке.
Тогда как определить, когда код будет работать как скомпилированный или интерпретированный?
Редактировать: если он скомпилирован в файл байт-кода.pyc, а затем интерпретирован. Тогда почему в первом примере оператор else не обнаружен?
4 ответа
Напротив. Python компилирует исходный код в байтовый код перед выполнением. Первый случай вызовет синтаксическую ошибку на этапе компиляции.
Во втором случае компилятор видит, что x
модифицируется в функции, так что связывает x
к объекту функции. Пространство имен функции создается каждый раз, когда функция вызывается, но переменная появляется только тогда, когда она была назначена. Это только когда вы выполняете print(x)
этот питон понимает, что вы запрашиваете локальную переменную, которая не была назначена. Это распространенная ошибка, когда не все пути выполнения устанавливают переменную так, как должны.
Слегка модифицируя пример, иногда локальная переменная устанавливается, а иногда нет. Когда установлено, вы видите переменную в местных и печати работает. Если эта переменная не задана, переменная отсутствует в локальных файлах, и при печати происходит сбой.
x = 10
def foo(val):
if val:
x = 1
print(val, 'before', locals())
print(x)
print(val, 'after')
x += 1
foo(True)
foo(False)
Выход
True before {'val': True, 'x': 1}
1
True after
False before {'val': False}
Traceback (most recent call last):
File "o.py", line 11, in <module>
foo(False)
File "o.py", line 6, in foo
print(x)
UnboundLocalError: local variable 'x' referenced before assignment
Хорошо, после прочтения ответов Дуга и Джима, я думаю, у меня есть идея о том, как это работает. Прежде всего, примеры работают в REPL (Ipython, по умолчанию)
Файлы: если вы напишите это в файле:
if True:
print("Hi")
else:
I am an error. What can you do about it?
И запустите файл, он выдаст ошибку SyntaxError. Это доказывает, что всякий раз, когда мы выполняем код Python из файла, он генерирует байт-код, и поскольку оператор else не является допустимым выражением Python, мы получаем ошибку SyntaxError.
REPL: с REPL все становится немного зависимым. В интерпретаторе Python, если вы печатаете
>>>def foo():
if True:
print("Hey")
else:
I am an error. What can you do about it?
>>>foo()
Hey
Успешное выполнение означает отсутствие байт-кода, верно? Оставайтесь на линии.
Если вы напишите это:
>>>x = 10
>>>def foo():
print(x)
x += 1
>>>foo()
И бум! все разваливается, вы получите UnboundLocalError в операторе print (x). Что означает, что там есть байт-код.
Так что именно здесь происходит?
Если python находит одно вхождение переменной, он пытается оптимизировать ее работу, сначала прочитав все из них. Итак, во втором примере, когда код встречает print(x), он пытается найти все операции с x. Вскоре он находит утверждение х +=1. Поскольку в локальной области видимости x не упоминается, а python никогда не ищет переменную в глобальной области видимости, если это не указано явно, мы имеем
UnboundLocalError: local variable 'x' is referenced before assignment
Неопровержимое доказательство
Если мы напишем что-то вроде этого:
>>>x = 10
>>>def foo():
if True:
print(x)
else:
x+=1
>>>foo()
UnboundLocalError: local variable 'x' referenced before assignment
Это оно!
x + = 1 никогда не собирался выполняться, но поскольку оператор print печатает x, а другая ссылка (x+=1) была проблемой, возникла ошибка перед печатью значения. Первый случай работал нормально без SyntaxError в REPL, потому что он никогда не удосужился заглянуть внутрь оператора else, потому что он никогда не имел значения.
Это всегда работает как интерпретировано; но проверка синтаксиса вызывается для всего файла в качестве первого шага, который генерирует файл байт-кода.
В обоих версиях Python 2.7 и 3.5 код, который вы поместите вверху, приведет к синтаксической ошибке:
python3 junk.py
File "junk.py", line 4
I am an error. what can you do about it?
^
SyntaxError: invalid syntax
Не должно быть возможности получить синтаксическую ошибку во время выполнения в python; единственный способ добиться этого - динамически импортировать модуль с синтаксической ошибкой.
Я не уверен, что понял ваш вопрос, но ошибка во втором случае является ошибкой во время выполнения; это как делать int x = 0; y = 10/x;
в С; переменная область действия ("существует ли x в этой точке?") не разрешается во время синтаксического анализа в python.
-
Редактировать; вот дамп из моего терминала:
Clank:tmp doug$ cat junk.py
if True:
print("Hello")
else:
I am an error. what can you do about it?
Clank:tmp doug$ python junk.py
File "junk.py", line 4
I am an error. what can you do about it?
^
SyntaxError: invalid syntax
Clank:tmp doug$ python3 junk.py
File "junk.py", line 4
I am an error. what can you do about it?
^
SyntaxError: invalid syntax
Если вы выполняете это через интерактивный REPL некоторого короткого (а не REPL по умолчанию Pythons), вы можете получить это, чтобы фактически выполнить без ошибок (IPython
s 'qtconsole разрешает это без проблем, например). Почему это разрешено, зависит исключительно от REPL и его реализации.
В Python это SyntaxError
; для него не может быть сгенерировано никакого байт-кода:
s ="""
i= 1
if i:
print("Hello")
else:
I am an error. what can you do about it?
"""
c = compile(s, '', mode='exec')
File "<string>", line 6
I am an error. what can you do about it?
^
SyntaxError: invalid syntax
Грамматика, в частности, не допускает этого, разрешается размещать только строковые литералы с пробелами между ними ( которые позже объединяет Python), а имена - нет (одно отдельное имя, конечно, допустимо). Короче говоря, это терпит неудачу на этапе анализа:
from parser import suite
st = suite(s)
File "<string>", line 6
I am an error. what can you do about it?
^
SyntaxError: invalid syntax
Python не знает, что делать, когда он видит два имени, разделенных пробелом, пробел не подразумевает каких-либо операций (кроме str
литералы).