Skip to main content
 首页 » 编程设计

python之不同的语法错误在输出中隐藏行

2025年05月04日19JustinYoung

我有一个调用编译的脚本。

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两个地方的异常:

  1. 解析时,由Python语法驱动
  2. 从解析结果创建抽象语法树 (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 = 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 来修复它一个新的异常(exception),空字符串替换为您的源文本:

>>> 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 文法解析器无法检测赋值目标中的文字的确切原因感兴趣,您可能会对 Guido van Rossum 在 writing a different parser for Python 中所做的工作感兴趣。 ,其中包括对当前解析器为何以这种方式工作以及替代解析器模型如何避免这些问题的阐述。