我有一个调用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
知道这是为什么吗?
Python 会在两个地方产生SyntaxError
异常:
- 解析时,由 Python 语法驱动
- 从分析结果创建抽象语法树 (AST( 时;AST 驱动编译器。
这是因为 Python 语法有一些特殊情况,在这些情况下,保持语法简单更容易,但是一旦解析完成并构建 AST 以完成额外的语法检查,就会进行额外的检查。
分配就是其中之一,因为=
标志左侧允许的规则与右侧允许的规则不同,但仍然密切相关。左侧是目标侧,目标的结构可以像列表或元组(解包分配(一样,您可以分配给属性或索引操作(listobj[1] = ...
等(。 但是要让解析器检测到目标实际上是文字而不是变量名称或属性等,将需要非常不同的解析器结构,因此将其留给 AST 处理。
因此,您的3 = 3
错误通过了解析阶段,但在后面的 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 = 3an'))
请注意异常中的('test', 1, 6, '3 = 3an')
元组;这些元组可通过源代码行本身的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()
以便在构建 AST 时发现SyntaxError
异常时,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 语法解析器无法检测赋值目标中的文字感兴趣,你可能会对 Guido van Rossum 在为 Python 编写不同的解析器时所做的工作感兴趣,其中包括说明为什么当前解析器的工作方式以及替代解析器模型如何避免这些问题。