我有一些代码的情况,其中eval()作为一个可能的解决方案。现在,我以前从未使用过eval(),但是,我遇到了大量关于它可能导致的潜在危险的信息。也就是说,我对使用它非常谨慎。

我的情况是,我有输入是由一个用户:

datamap = input('Provide some data here: ')

其中数据地图需要是一个字典。我搜索了一下,发现eval()可以解决这个问题。我认为我可以在尝试使用数据之前检查输入的类型,这将是一个可行的安全预防措施。

datamap = eval(input('Provide some data here: ')
if not isinstance(datamap, dict):
    return

我通读了所有的文件,但我仍然不清楚这样做是否安全。eval是否在数据输入或调用数据映射变量后立即计算数据?

ast模块的.literal_eval()是唯一安全的选项吗?


当前回答

如果您所需要的只是一个用户提供的字典,可能更好的解决方案是json.loads。主要的限制是JSON字典(“对象”)需要字符串键。你也只能提供文字数据,但ast.literal_eval也是如此。

其他回答

在最近的Python3 AST .literal_eval()不再解析简单的字符串,相反,你应该使用AST .parse()方法来创建一个AST,然后解释它。

这是在Python 3.6+中正确使用ast.parse()安全求值简单算术表达式的完整示例。

import ast, operator, math
import logging

logger = logging.getLogger(__file__)

def safe_eval(s):

    def checkmath(x, *args):
        if x not in [x for x in dir(math) if not "__" in x]:
            raise SyntaxError(f"Unknown func {x}()")
        fun = getattr(math, x)
        return fun(*args)

    binOps = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.Mod: operator.mod,
        ast.Pow: operator.pow,
        ast.Call: checkmath,
        ast.BinOp: ast.BinOp,
    }

    unOps = {
        ast.USub: operator.neg,
        ast.UAdd: operator.pos,
        ast.UnaryOp: ast.UnaryOp,
    }

    ops = tuple(binOps) + tuple(unOps)

    tree = ast.parse(s, mode='eval')

    def _eval(node):
        if isinstance(node, ast.Expression):
            logger.debug("Expr")
            return _eval(node.body)
        elif isinstance(node, ast.Str):
            logger.debug("Str")
            return node.s
        elif isinstance(node, ast.Num):
            logger.debug("Num")
            return node.value
        elif isinstance(node, ast.Constant):
            logger.info("Const")
            return node.value
        elif isinstance(node, ast.BinOp):
            logger.debug("BinOp")
            if isinstance(node.left, ops):
                left = _eval(node.left)
            else:
                left = node.left.value
            if isinstance(node.right, ops):
                right = _eval(node.right)
            else:
                right = node.right.value
            return binOps[type(node.op)](left, right)
        elif isinstance(node, ast.UnaryOp):
            logger.debug("UpOp")
            if isinstance(node.operand, ops):
                operand = _eval(node.operand)
            else:
                operand = node.operand.value
            return unOps[type(node.op)](operand)
        elif isinstance(node, ast.Call):
            args = [_eval(x) for x in node.args]
            r = checkmath(node.func.id, *args)
            return r
        else:
            raise SyntaxError(f"Bad syntax, {type(node)}")

    return _eval(tree)


if __name__ == "__main__":
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    logger.addHandler(ch)
    assert safe_eval("1+1") == 2
    assert safe_eval("1+-5") == -4
    assert safe_eval("-1") == -1
    assert safe_eval("-+1") == -1
    assert safe_eval("(100*10)+6") == 1006
    assert safe_eval("100*(10+6)") == 1600
    assert safe_eval("2**4") == 2**4
    assert safe_eval("sqrt(16)+1") == math.sqrt(16) + 1
    assert safe_eval("1.2345 * 10") == 1.2345 * 10

    print("Tests pass")

Python在求值时很热心,所以eval(input(…))(Python 3)会在用户输入到达eval时立即求值,不管你之后对数据做了什么。因此,这是不安全的,特别是在对用户输入求值时。

使用ast.literal_eval。


举个例子,在提示符下输入这个可能对你非常不利:

__import__('os').system('rm -rf /a-path-you-really-care-about')

如果您所需要的只是一个用户提供的字典,可能更好的解决方案是json.loads。主要的限制是JSON字典(“对象”)需要字符串键。你也只能提供文字数据,但ast.literal_eval也是如此。

ast.literal_eval()只考虑Python语法的一小部分是有效的:

提供的字符串或节点只能由以下Python文字结构组成:字符串、字节、数字、元组、列表、字典、集、布尔值和None。

通过__import__(“操作系统”)。System ('rm -rf /a-path-you-really-care-about')转换为ast.literal_eval()将引发一个错误,但eval()将很高兴地删除您的文件。

因为看起来你只是让用户输入一个普通的字典,所以使用ast.literal_eval()。它安全地做你想做的事,仅此而已。

datamap = eval(input('在这里提供一些数据:)))意味着你在判断代码是否不安全之前实际评估了代码。一旦函数被调用,它就对代码求值。另见eval的危险。

ast.literal_eval在输入不是有效的Python数据类型时引发异常,因此如果不是,代码将不会执行。

在需要eval时使用ast.literal_eval。通常不应该计算Python语句的字面值。