怎样实现公式运算脚本

wen 实用脚本 23

本文目录导读:

怎样实现公式运算脚本

  1. 基础方案:使用内置函数
  2. 自定义表达式解析器
  3. 使用专业库(推荐)
  4. 完整的公式引擎实现
  5. 安全建议和最佳实践
  6. 高级特性实现

我来详细介绍如何实现一个公式运算脚本系统,我将从简单到复杂提供几种实现方案。

基础方案:使用内置函数

Python实现(最简单)

# 使用内置的eval函数(注意安全风险)
def calculate_safe(expression, variables=None):
    """安全计算表达式"""
    if variables is None:
        variables = {}
    # 允许的数学函数和操作
    allowed_names = {
        'abs': abs, 'max': max, 'min': min,
        'round': round, 'sum': sum,
        'pow': pow, 'sqrt': lambda x: x**0.5
    }
    # 合并用户变量
    allowed_names.update(variables)
    try:
        # 编译表达式
        code = compile(expression, '<string>', 'eval')
        # 验证代码是否为安全操作
        for name in code.co_names:
            if name not in allowed_names:
                raise NameError(f"未允许的操作: {name}")
        return eval(code, {"__builtins__": {}}, allowed_names)
    except Exception as e:
        return f"计算错误: {str(e)}"
# 使用示例
variables = {'x': 10, 'y': 5}
result = calculate_safe("x + y * 2", variables)
print(f"计算结果: {result}")  # 输出: 20

自定义表达式解析器

使用词法分析和语法分析

import re
from enum import Enum
class TokenType(Enum):
    NUMBER = 'NUMBER'
    PLUS = 'PLUS'
    MINUS = 'MINUS'
    MULTIPLY = 'MULTIPLY'
    DIVIDE = 'DIVIDE'
    LPAREN = 'LPAREN'
    RPAREN = 'RPAREN'
    VARIABLE = 'VARIABLE'
    POWER = 'POWER'
class Token:
    def __init__(self, type, value):
        self.type = type
        self.value = value
class Lexer:
    def __init__(self, text):
        self.text = text
        self.pos = 0
        self.tokens = []
        self.current_char = text[0] if text else None
    def error(self):
        raise Exception('非法字符')
    def advance(self):
        self.pos += 1
        if self.pos < len(self.text):
            self.current_char = self.text[self.pos]
        else:
            self.current_char = None
    def skip_whitespace(self):
        while self.current_char and self.current_char.isspace():
            self.advance()
    def number(self):
        result = ''
        while self.current_char and (self.current_char.isdigit() or self.current_char == '.'):
            result += self.current_char
            self.advance()
        return float(result)
    def variable(self):
        result = ''
        while self.current_char and (self.current_char.isalnum() or self.current_char == '_'):
            result += self.current_char
            self.advance()
        return result
    def tokenize(self):
        while self.current_char:
            if self.current_char.isspace():
                self.skip_whitespace()
                continue
            if self.current_char.isdigit() or self.current_char == '.':
                self.tokens.append(Token(TokenType.NUMBER, self.number()))
            elif self.current_char.isalpha() or self.current_char == '_':
                self.tokens.append(Token(TokenType.VARIABLE, self.variable()))
            elif self.current_char == '+':
                self.tokens.append(Token(TokenType.PLUS, '+'))
                self.advance()
            elif self.current_char == '-':
                self.tokens.append(Token(TokenType.MINUS, '-'))
                self.advance()
            elif self.current_char == '*':
                self.advance()
                if self.current_char == '*':
                    self.tokens.append(Token(TokenType.POWER, '**'))
                    self.advance()
                else:
                    self.tokens.append(Token(TokenType.MULTIPLY, '*'))
            elif self.current_char == '/':
                self.tokens.append(Token(TokenType.DIVIDE, '/'))
                self.advance()
            elif self.current_char == '(':
                self.tokens.append(Token(TokenType.LPAREN, '('))
                self.advance()
            elif self.current_char == ')':
                self.tokens.append(Token(TokenType.RPAREN, ')'))
                self.advance()
            else:
                self.error()
        return self.tokens
class Parser:
    def __init__(self, tokens, variables=None):
        self.tokens = tokens
        self.pos = 0
        self.current_token = tokens[0] if tokens else None
        self.variables = variables or {}
    def error(self):
        raise Exception('语法错误')
    def eat(self, token_type):
        if self.current_token and self.current_token.type == token_type:
            self.pos += 1
            if self.pos < len(self.tokens):
                self.current_token = self.tokens[self.pos]
            else:
                self.current_token = None
        else:
            self.error()
    def factor(self):
        token = self.current_token
        if token.type == TokenType.NUMBER:
            self.eat(TokenType.NUMBER)
            return token.value
        elif token.type == TokenType.VARIABLE:
            self.eat(TokenType.VARIABLE)
            if token.value in self.variables:
                return self.variables[token.value]
            else:
                raise Exception(f"未定义的变量: {token.value}")
        elif token.type == TokenType.LPAREN:
            self.eat(TokenType.LPAREN)
            result = self.expr()
            self.eat(TokenType.RPAREN)
            return result
        elif token.type == TokenType.MINUS:
            self.eat(TokenType.MINUS)
            return -self.factor()
    def term(self):
        result = self.factor()
        while self.current_token and self.current_token.type in (TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.POWER):
            token = self.current_token
            if token.type == TokenType.MULTIPLY:
                self.eat(TokenType.MULTIPLY)
                result *= self.factor()
            elif token.type == TokenType.DIVIDE:
                self.eat(TokenType.DIVIDE)
                divisor = self.factor()
                if divisor == 0:
                    raise Exception("除零错误")
                result /= divisor
            elif token.type == TokenType.POWER:
                self.eat(TokenType.POWER)
                result **= self.factor()
        return result
    def expr(self):
        result = self.term()
        while self.current_token and self.current_token.type in (TokenType.PLUS, TokenType.MINUS):
            token = self.current_token
            if token.type == TokenType.PLUS:
                self.eat(TokenType.PLUS)
                result += self.term()
            elif token.type == TokenType.MINUS:
                self.eat(TokenType.MINUS)
                result -= self.term()
        return result
    def parse(self):
        return self.expr()
# 使用示例
def evaluate_expression(expression, variables=None):
    lexer = Lexer(expression)
    tokens = lexer.tokenize()
    parser = Parser(tokens, variables)
    return parser.parse()
# 测试
formula = "x * (y + 2) - 5"
vars = {'x': 10, 'y': 3}
result = evaluate_expression(formula, vars)
print(f"{formula} = {result}")  # 输出: 45.0

使用专业库(推荐)

sympy库

import sympy as sp
# 定义符号变量
x, y = sp.symbols('x y')
# 公式定义
formula = x**2 + 2*x*y + y**2
# 代入数值
result = formula.subs({x: 3, y: 4})
print(f"计算结果: {result}")  # 输出: 49
# 公式简化
simplified = sp.simplify((x+y)**2 - (x**2 + 2*x*y + y**2))
print(f"简化结果: {simplified}")  # 输出: 0
# 求导
derivative = sp.diff(formula, x)
print(f"导数: {derivative}")  # 输出: 2*x + 2*y

numexpr库(高性能)

import numexpr as ne
import numpy as np
# 简单计算
result = ne.evaluate('2*3 + 4')
print(f"简单计算: {result}")
# 向量计算
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = ne.evaluate('a * b + 10')
print(f"向量计算: {result}")
# 复杂公式
x = np.arange(10)
result = ne.evaluate('x**2 + 3*x + 1')
print(f"数组计算: {result}")

完整的公式引擎实现

import re
import math
from typing import Dict, Any, Optional
class FormulaEngine:
    """完整的公式计算引擎"""
    # 内置函数映射
    BUILTIN_FUNCTIONS = {
        'abs': abs,
        'max': max,
        'min': min,
        'pow': pow,
        'sqrt': math.sqrt,
        'sin': math.sin,
        'cos': math.cos,
        'tan': math.tan,
        'log': math.log,
        'log10': math.log10,
        'exp': math.exp,
        'ceil': math.ceil,
        'floor': math.floor,
        'round': round,
        'pi': math.pi,
        'e': math.e
    }
    def __init__(self):
        self.variables = {}
        self.custom_functions = {}
    def set_variable(self, name: str, value: Any):
        """设置变量"""
        self.variables[name] = value
    def set_variables(self, variables: Dict[str, Any]):
        """批量设置变量"""
        self.variables.update(variables)
    def add_function(self, name: str, func):
        """添加自定义函数"""
        self.custom_functions[name] = func
    def validate_formula(self, formula: str) -> bool:
        """验证公式合法性"""
        try:
            # 检查括号匹配
            stack = []
            for char in formula:
                if char == '(':
                    stack.append(char)
                elif char == ')':
                    if not stack:
                        return False
                    stack.pop()
            if stack:
                return False
            # 检查非法字符
            valid_chars = r'[\w\s\+\-\*\/\(\)\.\,\^\%]'
            for char in formula:
                if char not in valid_chars and not char.isalpha() and not char.isdigit():
                    if char not in '_.':
                        return False
            return True
        except:
            return False
    def calculate(self, formula: str, **kwargs) -> Any:
        """计算公式"""
        # 合并变量
        all_vars = {**self.variables, **kwargs}
        all_funcs = {**self.BUILTIN_FUNCTIONS, **self.custom_functions}
        # 创建计算上下文
        context = {**all_vars, **all_funcs}
        try:
            # 编译公式
            code = compile(formula, '<string>', 'eval')
            # 验证所有名称都在允许的范围内
            for name in code.co_names:
                if name not in context:
                    raise NameError(f"未定义的变量或函数: {name}")
            # 安全执行
            result = eval(code, {"__builtins__": {}}, context)
            return result
        except ZeroDivisionError:
            return "错误: 除零"
        except NameError as e:
            return f"错误: {str(e)}"
        except Exception as e:
            return f"计算错误: {str(e)}"
    def batch_calculate(self, formulas: Dict[str, str], **kwargs) -> Dict[str, Any]:
        """批量计算公式"""
        results = {}
        for name, formula in formulas.items():
            results[name] = self.calculate(formula, **kwargs)
        return results
# 使用示例
engine = FormulaEngine()
# 设置变量
engine.set_variable('price', 100)
engine.set_variable('tax_rate', 0.08)
# 添加自定义函数
def discount(price, rate):
    return price * (1 - rate)
engine.add_function('discount', discount)
# 计算公式
formulas = {
    'total_price': 'price * (1 + tax_rate)',
    'discount_price': 'discount(price, 0.2)',
    'monthly_payment': 'total_price / 12 + 10',
    'complex': 'sqrt(price) + pow(tax_rate, 2)'
}
results = engine.batch_calculate(formulas)
for name, result in results.items():
    print(f"{name}: {result}")

安全建议和最佳实践

完整的公式脚本系统

import ast
import operator
from typing import Any, Dict
class SafeFormulaEvaluator:
    """安全公式计算器"""
    # 允许的操作符
    OPERATORS = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.Pow: operator.pow,
        ast.USub: operator.neg,
        ast.Mod: operator.mod
    }
    def __init__(self):
        self.variables = {}
        self.functions = {}
        # 注册基本数学函数
        self.register_math_functions()
    def register_math_functions(self):
        """注册数学函数"""
        import math
        math_funcs = [
            'abs', 'max', 'min', 'pow', 'sqrt',
            'sin', 'cos', 'tan', 'log', 'log10',
            'exp', 'ceil', 'floor', 'round'
        ]
        for func_name in math_funcs:
            if hasattr(math, func_name):
                self.functions[func_name] = getattr(math, func_name)
            elif func_name == 'abs':
                self.functions[func_name] = abs
            elif func_name in ('max', 'min'):
                self.functions[func_name] = globals()[func_name]
    def evaluate(self, expression: str, variables: Dict[str, Any] = None) -> Any:
        """安全评估表达式"""
        if variables:
            self.variables.update(variables)
        try:
            tree = ast.parse(expression, mode='eval')
            return self._evaluate_node(tree.body)
        except Exception as e:
            raise ValueError(f"计算公式失败: {str(e)}")
    def _evaluate_node(self, node):
        """评估AST节点"""
        if isinstance(node, ast.Expression):
            return self._evaluate_node(node.body)
        elif isinstance(node, ast.Constant):
            return node.value
        elif isinstance(node, ast.Name):
            if node.id in self.variables:
                return self.variables[node.id]
            elif node.id in self.functions:
                return self.functions[node.id]
            else:
                raise NameError(f"未定义: {node.id}")
        elif isinstance(node, ast.BinOp):
            left = self._evaluate_node(node.left)
            right = self._evaluate_node(node.right)
            op = self.OPERATORS[type(node.op)]
            return op(left, right)
        elif isinstance(node, ast.UnaryOp):
            operand = self._evaluate_node(node.operand)
            op = self.OPERATORS[type(node.op)]
            return op(operand)
        elif isinstance(node, ast.Call):
            func = self._evaluate_node(node.func)
            args = [self._evaluate_node(arg) for arg in node.args]
            if callable(func):
                return func(*args)
            raise TypeError(f"不可调用的对象: {func}")
        elif isinstance(node, ast.List):
            return [self._evaluate_node(item) for item in node.elts]
        elif isinstance(node, ast.Tuple):
            return tuple(self._evaluate_node(item) for item in node.elts)
        else:
            raise NotImplementedError(f"不支持的节点类型: {type(node).__name__}")
# 使用示例
evaluator = SafeFormulaEvaluator()
# 设置变量
variables = {
    'x': 10,
    'y': 20,
    'z': 5
}
# 测试各种公式
test_formulas = [
    "x + y",
    "x * y - z",
    "sqrt(x**2 + y**2)",
    "sin(x) + cos(y)",
    "max(x, y, z)",
    "(x + y) / z + pow(x, 2)"
]
for formula in test_formulas:
    try:
        result = evaluator.evaluate(formula, variables)
        print(f"{formula} = {result}")
    except Exception as e:
        print(f"错误: {formula} -> {str(e)}")

高级特性实现

条件表达式和循环

class AdvancedFormulaEngine:
    """高级公式引擎,支持条件和循环"""
    def __init__(self):
        self.variables = {}
        self.functions = {}
        self.condition_ops = {
            '==': operator.eq,
            '!=': operator.ne,
            '<': operator.lt,
            '>': operator.gt,
            '<=': operator.le,
            '>=': operator.ge,
            'and': lambda x, y: x and y,
            'or': lambda x, y: x or y,
            'not': operator.not_
        }
    def evaluate(self, expression, variables=None):
        if variables:
            self.variables.update(variables)
        return self._parse_and_execute(expression)
    def _parse_and_execute(self, expr):
        # 处理条件表达式
        if ' if ' in expr and ' else ' in expr:
            # 三元运算符
            pattern = r'^(.+?)\s+if\s+(.+?)\s+else\s+(.+)$'
            match = re.match(pattern, expr)
            if match:
                true_val = match.group(1)
                condition = match.group(2)
                false_val = match.group(3)
                if self._parse_and_execute(condition):
                    return self._parse_and_execute(true_val)
                else:
                    return self._parse_and_execute(false_val)
        # 使用安全评估
        return self._safe_eval(expr)
    def _safe_eval(self, expression):
        """安全评估表达式"""
        # 简化实现,实际应考虑所有安全因素
        allowed_context = {
            **self.variables,
            **self.functions,
            **self.condition_ops
        }
        try:
            return eval(expression, {"__builtins__": {}}, allowed_context)
        except Exception as e:
            raise ValueError(f"计算错误: {str(e)}")
# 使用示例
engine = AdvancedFormulaEngine()
engine.variables = {'x': 15, 'y': 10, 'z': 8}
formula = "x if x > y else y"
result = engine.evaluate(formula)
print(f"条件结果: {result}")  # 输出: 15

实现公式运算脚本的主要方法:

  1. 简单场景:使用 eval()numexpr
  2. 中等复杂度:自定义词法解析器
  3. 专业需求:使用 sympynumexpr 等专业库
  4. 完整系统:构建安全的公式引擎

选择建议

  • 快速原型:使用 Python 的 eval()(注意安全)
  • 生产环境:使用 numexpr 或自定义解析器
  • 科学计算:使用 sympy
  • 高性能需求:使用 numexpr 或编译为字节码

记得始终考虑安全性,对用户输入的公式进行验证和限制。

抱歉,评论功能暂时关闭!