Различные синтаксические ошибки скрывают строку в выводе - PullRequest
1 голос
/ 05 октября 2019

У меня есть скрипт, который вызывает compile.

try:
    code = compile('3 = 3', 'test', 'exec')
except Exception as e:
    sys.stderr.write(''.join(traceback.format_exception_only(type(e), e)))

3 = 3, что приводит к:

File "test", line 1
SyntaxError: can't assign to literal

В то время как 3 = 3a фактически печатает строку

File "test", line 1
    3 = 3a
        ^
SyntaxError: invalid syntax

Есть идеи, почему это так?

1 Ответ

5 голосов
/ 05 октября 2019

Python создает SyntaxError исключения в двух местах:

  1. при синтаксическом анализе, который определяется грамматикой Python
  2. при создании Абстрактного синтаксического дерева (AST) из анализарезультат;AST управляет компилятором.

Это связано с тем, что в грамматике Python есть несколько особых случаев, когда было проще сохранить грамматику простой, но затем выполнить дополнительные проверки после завершения анализа ипостроение AST, где выполняются дополнительные проверки синтаксиса.

Назначения являются одним из таких мест, потому что правила для того, что разрешено слева от знака =, отличается от того, что разрешено справа, но все ещетесно связаны. Левая сторона - это сторона target , и цели могут быть структурированы как списки или кортежи (распаковка назначений), и вы можете назначать атрибуты или операции индексирования (listobj[1] = ... и т. Д.). Но чтобы синтаксический анализатор обнаружил, что цель на самом деле является литералом, а не именем или атрибутом переменной, и т. Д., Потребуется совсем другая структура синтаксического анализатора, так что вместо этого это остается за AST. * ошибка проходит этап синтаксического анализа, но затем завершается неудачей на более поздней фазе проверки AST «назначение назначения», в то время как 3 = 3a попадает на этап синтаксического анализа (где 3a легко определить как ошибку). дать вам хорошую синтаксическую ошибку, исключения, созданные анализатором, содержат строку исходного кода в исключении:

>>> try:
...     code = compile('3 = 3a', 'test', 'exec')
... except Exception as e:
...     print(repr(e))
...
SyntaxError('invalid syntax', ('test', 1, 6, '3 = 3a\n'))

Обратите внимание на кортеж ('test', 1, 6, '3 = 3a\n') в исключении;они доступны через атрибуты SyntaxError filename, lineno (номер строки), offset (смещение столбца) и text для самой строки исходного кода. Для анализатора это легко сделать, так как он имеет доступ к исходному коду.

Но AST не имеет исходного кода. Он содержит только имя файла, номер строки, столбец и объекты дерева разбора . У него нет исходного текста. Обычно он пытается прочитать это из имени файла, но test на самом деле не файл. Таким образом, строка пуста:

>>> try:
...     code = compile('3 = 3', 'test', 'exec')
... except Exception as e:
...     print(repr(e))
...
SyntaxError('cannot assign to literal', ('test', 1, 1, ''))

Вы можете проверить это и исправить его, заменив исключение SyntaxError новым с пустой строкой, замененной исходным текстом:

>>> source = '3 = 3'
>>> try:
...     code = compile(source, 'test', 'exec')
... except Exception as e:
...     if isinstance(e, SyntaxError) and not e.text:
...         sline = source.splitlines(True)[e.lineno - 1]
...         e = SyntaxError(e.msg, (e.filename, e.lineno, e.offset, sline))
...     sys.stderr.write(''.join(traceback.format_exception_only(type(e), e)))
...
  File "test", line 1
    3 = 3
    ^
SyntaxError: cannot assign to literal

Обратите внимание, что для многострочной исходной строки вы хотите разбить этот источник на строки и использовать атрибут .lineno, чтобы выбрать указанную исходную строку.

Альтернатива - записать исходный код во временное имя файла и передать это имя в compile(), чтобы при обнаружении исключения SyntaxError при создании AST Python мог затем открыть этот временный файл инайти соответствующую текстовую строку.

Обратите внимание, что при использовании специального имени файла '<string>' не предпринимается попытка найти исходный код для строки, а для e.text установлено значение None:

>>> try:
...     code = compile('3 = 3', '<string>', 'exec')
... except Exception as e:
...     print(repr(e))
...
SyntaxError('cannot assign to literal', ('<string>', 1, 1, None))

и когда для атрибута .text установлено значение None, модуль traceback отказывается печатать раздел строки и маркера.

Если вас точно интересует, почему синтаксический анализатор Python не будетобнаружение литералов в цели назначения, вас может заинтересовать работа, которую Гвидо ван Россум делает в написании другого парсера для Python , который включает в себя объяснение того, почему текущий парсер работает так, как работает, и как альтернативаМодель парсера может избежать этих проблем.

...