如何使用Python AST解析器正确解析与反解析含多行字符串的脚本
使用Python内置ast模块处理包含多行三引号字符串的脚本时,反解析阶段会丢失多行字符串格式:原本的三引号多行字符串会被转换为带\n转义符的单行引号字符串,增加脚本转换类需求的实现成本。
复现代码:
import ast script = "text='''Line1\nLine2'''" code = ast.parse(script, mode='exec') print(ast.unparse(code)) node = code.body[0].value print(node.lineno, node.end_lineno)
运行输出:
text = 'Line1\nLine2' 1 2
从输出可以看到,AST节点本身正确记录了字符串的起止行号(从第1行到第2行),但默认的ast.unparse()逻辑没有利用这个信息还原多行字符串格式。
首先明确一个前提:Python标准库自带的ast.unparse()设计目标是输出语法等价的合法Python代码,并不保证和原始源码的格式完全一致,丢失多行字符串、注释、缩进风格、引号类型等格式信息是默认预期行为,不是功能bug。
可行的处理方案分两类:
- 自定义反解析逻辑适配多行字符串
标准库的ast._Unparser支持继承扩展,我们可以重写常量字符串的输出逻辑,对跨多行的字符串节点主动使用三引号格式输出:
import ast
class PreserveMultiStrUnparser(ast._Unparser):
def visit_Constant(self, node):
# 识别跨多行的字符串常量,用三引号输出
if isinstance(node.value, str) and node.end_lineno > node.lineno:
# 生产环境使用需要额外处理字符串内部包含三引号的转义场景
self.write(f'"""{node.value}"""')
return
super().visit_Constant(node)
def unparse_preserve_multistr(ast_tree):
unparser = PreserveMultiStrUnparser()
unparser.visit(ast_tree)
return unparser.f.getvalue()
script = "text='''Line1\nLine2'''"
code = ast.parse(script, mode='exec')
print(unparse_preserve_multistr(code))
上述测试代码的输出为:
text = """Line1
Line2"""
已经可以正确还原多行字符串格式。如果需要进一步匹配原始字符串使用的单三引号/双三引号风格,或者需要精准区分“真实三引号多行字符串”和“单行字符串内写`\n`转义后被编辑器折行”的场景,需要搭配`tokenize`模块读取原始源码的token流,给每个AST节点补充对应的原始格式信息——原生AST节点本身不存储引号类型、原始字面量写法这类格式信息。 - 高保真场景使用格式感知的AST处理工具 如果需求是修改代码后100%保留原始代码的所有格式(包括注释、空行、缩进、引号风格、换行位置等),不要使用原生`ast`+自定义unparse的方案,直接选择支持格式保真的AST处理工具即可,这类工具生成的语法树会完整保留原始token信息,反解析时不会出现格式丢失的问题。 内容的提问来源于stack exchange,提问作者Frank Blabu

