Python3中ast.Num/visit_Num解析负数返回符号错误问题
Python AST解析负数在Py2/Py3的兼容性问题
问题背景
使用Python内置ast(Abstract Syntax Trees,抽象语法树)模块手动解析表达式,全程未调用eval、exec或compile方法。原有解析逻辑在Python 2环境下运行正常,但迁移到Python 3环境后,解析表达式需要经过3层节点处理,最终返回结果符号异常:本应为负值的解析结果返回了正值。
Python 2 版本复现
Python 2环境下解析"-890"字符串时,直接读取节点属性即可得到正确的负值,复现代码如下:
def test(string_value): parse_expression = ast.parse(string_value) node = parse_expression.body for per in node: # return <_ast.Expr object at 0x7fa695c78510> print(per) # return <_ast.Num object at 0x7ff4b0b1de50> print(per.value) # return -890 print(per.value.n) pass pass test("-890")
运行后直接读取per.value.n就能得到正确结果-890。
Python 3 版本复现
Python 3环境下执行相同解析逻辑时,节点结构发生了变化,复现代码如下:
import ast def test(string_value): parse_expression = ast.parse(string_value) node = parse_expression.body for per in node: # return <_ast.Expr object at 0x7fe6a0ce34c0> print(per) #return <_ast.UnaryOp object at 0x7f0b92941790> print(per.value) print(dir(per.value)) print(per.value.col_offset) #0 print(per.value.end_col_offset) #4 print(per.value.end_lineno) #1 print(per.value.lineno) #1 print(per.value.op) # <_ast.USub object at 0x7f055c489610> print(per.value.operand) #<_ast.Constant object at 0x7f055c45d8e0> print(dir(per.value.op)) print(dir(per.value.operand)) print(per.value.operand.col_offset) #1 print(per.value.operand.end_col_offset) #4 print(per.value.operand.end_lineno) #1 print(per.value.operand.kind) #None print(per.value.operand.lineno) #1 # 返回正值而非预期负值 print(per.value.operand.n) #890 print(per.value.operand.s) #890 print(per.value.operand.value) #890 pass pass test("-890")
Python 3环境下执行上述代码可发现:解析"-890"时,Expr节点的value属性为UnaryOp类型而非ast.Num类型,其op属性为USub负号运算符,operand为存储数值的Constant节点,直接读取operand的n、s、value属性均只能得到正值890,无法直接获取带正确负号的数值。
问题根因
Python 3对负数字面量的AST生成规则做了调整:
- 负号不再作为数值字面量的一部分存储在
Num/Constant节点中,而是被识别为独立的一元运算符 - 整个负数表达式被拆分为「一元运算节点(负号)+ 正数常量节点」的两层结构,和正数的解析路径不再一致
兼容修复方案
解析数值节点时增加类型判断,遇到一元负号运算时主动对操作数做取反处理,同时兼容Py2、Py3两个版本的节点结构,示例实现如下:
import ast def extract_num(node): # 兼容Python2的Num节点、Python3直接为Constant的正数字面量 if isinstance(node, ast.Num): return node.n if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value # 处理Python3中负数的UnaryOp结构 if isinstance(node, ast.UnaryOp): if isinstance(node.op, ast.USub): return -extract_num(node.operand) if isinstance(node.op, ast.UAdd): return +extract_num(node.operand) raise ValueError(f"不支持的数值节点类型: {type(node)}") def test(string_value): parse_res = ast.parse(string_value) for expr_node in parse_res.body: print(extract_num(expr_node.value)) test("-890") # 所有Python版本下均正确输出-890 test("890") # 正确输出890 test("+123") # 正确输出123
内容的提问来源于stack exchange,提问作者Mark Anthony Libres
相关产品推荐
相关产品推荐

