You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 05:03:26