我有一个调用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。
分配就是其中之一,因为=
符号左侧允许的规则与右侧允许的规则不同,但仍然密切相关。左侧是target侧,目标的结构可以像列表或元组(拆包分配)一样,您可以分配给属性或索引操作(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 = 3a\n'))
请注意
('test', 1, 6, '3 = 3a\n')
元组为例外;通过源代码行本身的SyntaxError
属性filename
,lineno
(行号),offset
(列偏移)和text
,可以使用这些属性。对于解析器,这很容易提供,因为它可以访问源代码。
但是AST
没有源代码。它只有文件名,行号,列和parse tree objects。它没有原始源文本。通常会尝试从文件名中读取该文件,但是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在writing a different parser for Python中所做的工作感兴趣,其中包括当前解析器为何能工作的说明。的方式以及替代解析器模型如何避免这些问题。