在Python中编译和执行简单的用户定义代码

在Python中编译和执行简单的用户定义代码,python,Python,我想让我的用户能够为一个项目运行非常简单的Python函数。当然,我会想到eval(),但这是一个巨大的风险。经过一段时间的思考,我意识到用户可能需要的大多数函数都是非常基本的,类似于最常见的excel函数。因此,我想维护一个字典,其中键是函数名,用户只能选择(由我)在该字典中定义的函数。例如: def add(a, b): return a + b def sum(numbers): result = 0 for number in numbers:

我想让我的用户能够为一个项目运行非常简单的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)
,因为键在我的字典中不存在,那么会出现错误。我的问题是:这有多安全?是否存在我在这里忽略的任何潜在漏洞?

您可以使用适当的
全局变量和
局部变量来对
进行评估。例如,这是我在中使用的wat

但你可能也会事先筛选表达式。拒绝包含
import
eval
exec
的内容:

if any(j in expr for j in ('import', 'exec', 'eval')):
    raise ValueError('import, exec and eval are not allowed')
上面链接的模块还包含使用
ast
将Python计算转换为LaTeX数学表达式。您还可以使用
ast
构建自定义表达式计算器

另外,这里是我制作的一个小堆栈

一个不同之处是,我向
\u 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]
想想也许是最近的岔口
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]