首页 > 解决方案 > 在 Python 中编译和执行简单的用户定义代码

问题描述

我想让我的用户能够为项目运行非常简单的 Python 函数。当然,eval()想到了,但这是一个巨大的风险。想了想,发现用户可能需要的大部分功能都很简陋,类似于最常见的excel功能。所以我正在考虑维护一个字典,其中键是函数名称,用户只能选择在该字典中定义的函数(由我定义)。例如:

def add(a, b):
    return a + b

def sum(numbers):
    result = 0
    for number in numbers:
        result += number
    return number

...

function_map = {
    'add': add,
    'sum': sum,
    ...
}

现在,如果用户将一行定义为add(4, 5),则结果是预期的 9,但是,如果他们定义类似的内容foo(4),由于我的字典中不存在该键,则会引发错误。我的问题是:这有多安全?我在这里忽略了任何潜在的漏洞吗?

标签: python

解决方案


您可以通过使用适当的和论点来eval 稍微去除牙牙。例如,这是我在一种计算器中使用的 wat 。globalslocals

# To make eval() less dangerous by removing access
# to built-in functions.
_globals = {"__builtins__": None}
# But make standard math functions available.
_lnames = (
    'acos', 'asin', 'atan', 'ceil', 'cos', 'cosh', 'e', 'log',
    'log10', 'pi', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'radians'
)
_locals = {k: eval('math.' + k) for k in _lnames}

value = eval(expr, _globals, _locals)

但是您也应该事先筛选表达式。拒绝那些包含importorevalexec:

if any(j in expr for j in ('import', 'exec', 'eval')):
    raise ValueError('import, exec and eval are not allowed')

上面链接的模块还包含ast将 Python 计算转换为 LaTeX 数学表达式的使用。您还可以使用ast来构建自定义表达式评估器。

否则,这是我制作的一个基于堆栈的小型后缀表达式评估器

一个区别是我将每个运算符需要的参数数量添加到_ops值中,这样我就知道要从堆栈中取出多少个操作数。

import operator
import math

# Global constants {{{1
_add, _sub, _mul = operator.add, operator.sub, operator.mul
_truediv, _pow, _sqrt = operator.truediv, operator.pow, math.sqrt
_sin, _cos, _tan, _radians = math.sin, math.cos, math.tan, math.radians
_asin, _acos, _atan = math.asin, math.acos, math.atan
_degrees, _log, _log10 = math.degrees, math.log, math.log10
_e, _pi = math.e, math.pi
_ops = {
    '+': (2, _add),
    '-': (2, _sub),
    '*': (2, _mul),
    '/': (2, _truediv),
    '**': (2, _pow),
    'sin': (1, _sin),
    'cos': (1, _cos),
    'tan': (1, _tan),
    'asin': (1, _asin),
    'acos': (1, _acos),
    'atan': (1, _atan),
    'sqrt': (1, _sqrt),
    'rad': (1, _radians),
    'deg': (1, _degrees),
    'ln': (1, _log),
    'log': (1, _log10)
}
_okeys = tuple(_ops.keys())
_consts = {'e': _e, 'pi': _pi}
_ckeys = tuple(_consts.keys())


def postfix(expression):  # {{{1
    """
    Evaluate a postfix expression.

    Arguments:
        expression: The expression to evaluate. Should be a string or a
                    sequence of strings. In a string numbers and operators
                    should be separated by whitespace

    Returns:
        The result of the expression.
    """
    if isinstance(expression, str):
        expression = expression.split()
    stack = []
    for val in expression:
        if val in _okeys:
            n, op = _ops[val]
            if n > len(stack):
                raise ValueError('not enough data on the stack')
            args = stack[-n:]
            stack[-n:] = [op(*args)]
        elif val in _ckeys:
            stack.append(_consts[val])
        else:
            stack.append(float(val))
    return stack[-1]

推荐阅读