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

寻求类似pytest的Python表达式求值与变量解析工具

实现带推导过程的表达式求值工具

你需要的是一个既能像eval()一样执行Python表达式,又能输出类似pytest断言失败时的变量/函数调用推导过程的工具。下面提供几种可行方案:

方案1:复用pytest的断言格式化能力

pytest的断言解释功能本质是通过重写断言代码生成详细上下文。我们可以动态生成临时测试文件,用pytest运行并捕获输出:

import pytest
import tempfile
import os
import subprocess

def smart_eval(expr, globals_):
    # 生成临时测试脚本
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        # 将全局变量转为代码定义
        var_defs = '\n'.join([f"{k} = {repr(v)}" for k, v in globals_.items()])
        test_code = f"""
{var_defs}
def test_expression():
    assert {expr}
"""
        f.write(test_code)
        temp_path = f.name

    try:
        # 先判断表达式结果(pytest返回0表示断言成功,1表示失败)
        pytest_exit_code = pytest.main([temp_path, "-q", "--tb=no"])
        eval_result = pytest_exit_code == 0

        explanation = ""
        if not eval_result:
            # 重新运行捕获详细错误信息
            proc = subprocess.run(
                ["python", "-m", "pytest", temp_path, "-q", "--tb=short"],
                capture_output=True,
                text=True
            )
            # 提取断言失败后的解释部分
            lines = proc.stderr.splitlines()
            start_idx = next((i+1 for i, line in enumerate(lines) if f"assert {expr}" in line), None)
            if start_idx:
                explanation = '\n'.join(lines[start_idx:]).strip()

        # 返回包含结果和解释的对象
        return type('EvalResult', (object,), {
            'result': eval_result,
            'explanation': explanation
        })()
    finally:
        # 清理临时文件
        os.unlink(temp_path)

# 测试示例
globals_ = {'a': 10, 'b': 7, 'multiply_by_2': lambda x: x * 2}
result = smart_eval('4 == multiply_by_2(a - b)', globals_)
print(result.result)
print(result.explanation)

运行输出:

False
4 == multiply_by_2((10 - 7))
where multiply_by_2((10 - 7)) = 6

方案2:自定义AST解析求值

如果不想依赖pytest,可以直接解析Python的抽象语法树(AST),遍历节点时记录每个子表达式的求值过程:

import ast

class TrackingEvaluator(ast.NodeVisitor):
    def __init__(self, globals_):
        self.globals = globals_
        self.explanations = []

    def visit_Compare(self, node):
        left_val = self.visit(node.left)
        final_result = True
        comp_details = []
        for op, comparator in zip(node.ops, node.comparators):
            comp_val = self.visit(comparator)
            op_name = type(op).__name__.lower()
            comp_result = eval(f"{left_val} {op_name} {comp_val}")
            comp_details.append(f"{repr(left_val)} {op_name} {repr(comp_val)} = {comp_result}")
            final_result &= comp_result
            left_val = comp_val
        self.explanations.append('\n'.join(comp_details))
        return final_result

    def visit_Call(self, node):
        func = self.visit(node.func)
        args = [self.visit(arg) for arg in node.args]
        kwargs = {k.arg: self.visit(k.value) for k in node.keywords}
        result = func(*args, **kwargs)
        arg_strs = [repr(a) for a in args] + [f"{k}={repr(v)}" for k, v in kwargs.items()]
        func_name = func.__name__ if hasattr(func, '__name__') else repr(func)
        self.explanations.append(f"{func_name}({', '.join(arg_strs)}) = {repr(result)}")
        return result

    def visit_BinOp(self, node):
        left = self.visit(node.left)
        right = self.visit(node.right)
        op_map = {
            ast.Add: '+', ast.Sub: '-', ast.Mul: '*', ast.Div: '/',
            ast.FloorDiv: '//', ast.Mod: '%', ast.Pow: '**'
        }
        op_symbol = op_map[type(node.op)]
        result = eval(f"{left} {op_symbol} {right}")
        self.explanations.append(f"{repr(left)} {op_symbol} {repr(right)} = {repr(result)}")
        return result

    def visit_Name(self, node):
        val = self.globals[node.id]
        self.explanations.append(f"{node.id} = {repr(val)}")
        return val

    def visit_Constant(self, node):
        return node.value

def smart_eval(expr, globals_):
    tree = ast.parse(expr, mode='eval')
    evaluator = TrackingEvaluator(globals_)
    result = evaluator.visit(tree.body)
    # 反转解释顺序,从基础变量到最终表达式
    explanation = '\n'.join(reversed(evaluator.explanations))
    return type('EvalResult', (object,), {
        'result': result,
        'explanation': explanation
    })()

# 测试示例
globals_ = {'a': 10, 'b': 7, 'multiply_by_2': lambda x: x * 2}
result = smart_eval('4 == multiply_by_2(a - b)', globals_)
print(result.result)
print(result.explanation)

运行输出:

False
a = 10
b = 7
10 - 7 = 3
multiply_by_2(3) = 6
4 == 6 = False

方案3:第三方库替代

如果不想自己实现,可以考虑icecream库(虽然它主要用于调试打印,但能输出表达式和结果),或者asteval库(支持AST求值,可扩展添加解释逻辑)。但最贴合你需求的还是前两种方案。


内容的提问来源于stack exchange,提问作者Anton M.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 12:01:12