【问题标题】:ValueError: malformed string when using ast.literal_evalValueError:使用 ast.literal_eval 时的字符串格式错误
【发布时间】:2013-12-23 17:22:19
【问题描述】:

众所周知,使用eval() 存在潜在的安全风险,因此提倡使用ast.literal_eval(node_or_string)

但是在 python 2.7 中,它在运行此示例时返回 ValueError: malformed string

>>> ast.literal_eval("4 + 9")

而在 python 3.3 中,此示例按预期工作:

>>> ast.literal_eval('4+9')
13

为什么它在 python 3 而不是 python 2 上运行?如何在不使用有风险的 eval() 函数的情况下在 python 2.7 中修复它?

【问题讨论】:

  • 这绝对不是字面意思,所以我想说 Python 3.3 在这里是错误的。也许这是窥视孔优化的意外结果?编辑:不,它在literal_eval 中是特殊的和常量折叠的。仅用于加法和减法,没有乘法或除法或其他运算符。

标签: python python-2.7 python-3.x


【解决方案1】:

这在 Python 2 上不起作用的原因在于它对 literal_eval 的实现。当右操作数是复数时,原始实现仅对加法和减法执行数字评估。这在语法上对于将复数表示为文字是必要的。

Python 3 中的 was changed 支持任何类型的有效数字表达式在加法和减法的任一侧。但是,literal_eval 的使用仍然仅限于加减法。

这主要是因为literal_eval 应该是一个将单个 constant 文字(表示为字符串)转换为 Python 对象的函数。对于简单的内置类型,有点像向后的repr。不包括实际的表达式求值,而且它适用于 Python 3 的事实只是其实现的一个很好的副作用。

为了评估实际表达式,而不必使用eval(我们不想这样做),我们可以编写自己的表达式评估算法,在 AST 上运行。这非常简单,尤其是对于数字的简单算术运算(例如构建您自己的计算器等)。我们只需将字符串解析为 AST,然后通过查看不同的节点类型并应用正确的操作来评估结果树。

类似这样的:

import ast, operator

binOps = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.div,
    ast.Mod: operator.mod
}

def arithmeticEval (s):
    node = ast.parse(s, mode='eval')

    def _eval(node):
        if isinstance(node, ast.Expression):
            return _eval(node.body)
        elif isinstance(node, ast.Str):
            return node.s
        elif isinstance(node, ast.Num):
            return node.n
        elif isinstance(node, ast.BinOp):
            return binOps[type(node.op)](_eval(node.left), _eval(node.right))
        else:
            raise Exception('Unsupported type {}'.format(node))

    return _eval(node.body)

如您所见,此实现非常简单。当然,它还不支持更复杂的东西,比如求幂和一些一元节点,但添加它并不太难。而且效果很好:

>>> arithmeticEval('4+2')
6
>>> arithmeticEval('4*1+2*6/3')
8

您甚至可以在以后引入更复杂的东西(例如,像 sin() 这样的函数调用)。

【讨论】:

  • 我认为在 python3 中,operator.div 被删除了。您可以改用 operator.truediv。
【解决方案2】:

这是为了支持复数(从issue 4907 开始)。例如,1 + 2j 被解析器解析为由整数文字、加法运算和imaginary literal 组成的表达式;但由于complex numbers 是内置类型,所以ast.literal_eval 最好支持复数语法。

2.x 和 3.x 之间的 change in behaviour 是为了支持以“错误的方式”写入复数,例如1j + 2;它允许任意加法或减法表达式的事实是(大部分是意外的)副作用。

如果要解析任意算术表达式,则应解析为语法树(使用ast.parse)、verify it with a whitelist,然后进行求值。

【讨论】:

  • +1,感谢您提供有关复数的信息!没想到添加是将它们作为文字所必需的语法的一部分。
【解决方案3】:

使用源代码,卢克!

http://hg.python.org/cpython/file/2.7/Lib/ast.py#l40 http://hg.python.org/cpython/file/3.2/Lib/ast.py#l39

你会在那里找到你的答案。具体来说,2.7 版本对line 70 有一个奇怪的限制,即 BinOp 的右节点是复杂的。

>>> sys.version
'2.7.3 (default, Sep 26 2013, 20:03:06) \n[GCC 4.6.3]'
>>> ast.literal_eval('9 + 0j')
(9 + 0j)
>>> ast.literal_eval('0j + 9')
ValueError: malformed string

我猜 2.7 的目的是允许 literal_eval 使用复杂的文字,例如像 9 + 0j 这样的数字,它从来没有打算做简单的整数加法。然后在 python 3 中,他们加强了literal_eval 来处理这些情况。

【讨论】:

  • this revision 中添加了 2.7 中的奇怪行为,似乎是为了解决 issue 4907
  • 最让我困惑的是为什么开发者没有在python 2中实现python 3的ast.literal_eval
  • 因为 2 + 3 不是文字。如果我猴子补丁+ 做一些不同的事情怎么办?我同意这里的 2.7 行为。在我看来,+/- 工作只是处理复数的丑陋副作用。如果支持加法,为什么不除法或乘法或...?
  • 确实,这最终被认为是 3.6 中的错误,现在已在 3.7 中修复。 issue31778
【解决方案4】:

使用pyparsing 拼凑一个简单的表达式求值器并不难。

假设您要评估以下表达式类型的表达式,包括括号:

2+3
4.0^2+5*(2+3+4)
1.23+4.56-7.890
(1+2+3+4)/5
1e6^2/1e7

SimpleCalc 示例的这种简化:

import pyparsing as pp
import re

ex='''\
2+3
4.0^2+5*(2+3+4)
1.23+4.56-7.890
(1+2+3+4)/5
1e6^2/1e7'''

e = pp.CaselessLiteral('E')
dec, plus, minus, mult, div, expop=map(pp.Literal,'.+-*/^')
addop  = plus | minus
multop = mult | div
lpar, rpar=map(pp.Suppress,'()')
p_m = plus | minus

num = pp.Word(pp.nums) 
integer = pp.Combine( pp.Optional(p_m) + num )
floatnumber = pp.Combine( integer +
                       pp.Optional( dec + pp.Optional(num) ) +
                       pp.Optional( e + integer ) )

stack=[]
def pushFirst(s, l, t):
    stack.append( t[0] )

expr=pp.Forward()
atom = ((floatnumber | integer).setParseAction(pushFirst) | 
         ( lpar + expr.suppress() + rpar )
       )

factor = pp.Forward()
factor << atom + pp.ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )

term = factor + pp.ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
expr << term + pp.ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )    

pattern=expr+pp.StringEnd()

opn = { "+" : ( lambda a,b: a + b ),
        "-" : ( lambda a,b: a - b ),
        "*" : ( lambda a,b: a * b ),
        "/" : ( lambda a,b: a / b ),
        "^" : ( lambda a,b: a ** b ) }

def evaluateStack(stk):
    op = stk.pop()
    if op in "+-*/^":
        op2 = evaluateStack(stk)
        op1 = evaluateStack(stk)
        return opn[op](op1, op2)
    elif re.search('^[-+]?[0-9]+$',op):
        return int(op)
    else:
        return float(op)     

for line in ex.splitlines():
    parse=pattern.parseString(line)   
    s=stack[:]
    print('"{}"->{} = {}'.format(line,s,evaluateStack(stack)))   

打印:

"2+3"->['2', '3', '+'] = 5
"4.0^2+5*(2+3+4)"->['4.0', '2', '^', '5', '2', '3', '+', '4', '+', '*', '+'] = 61.0
"1.23+4.56-7.890"->['1.23', '4.56', '+', '7.890', '-'] = -2.1000000000000005
"(1+2+3+4)/5"->['1', '2', '+', '3', '+', '4', '+', '5', '/'] = 2.0
"1e6^2/1e7"->['1E6', '2', '^', '1E7', '/'] = 100000.0

【讨论】:

  • +1 有趣的解决方案...使用pyparser 的优势是什么?它更安全还是更快?
  • 主要优点是对输入和输出的控制更多。使用解析器与使用 AST literal_eval 一样安全。它将验证您的输入是否符合您的期望,并且只会执行您告诉它的内容。这是一个很棒的工具。 AST 也是一个很棒的工具。
【解决方案5】:

@poke 答案的更新版本,它允许在 py3.x 或其他一元运算符中使用负数。因此,例如“-3”的计算结果为 -3,而不是错误。

import ast, operator

binOps = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Mod: operator.mod
}

unOps = {
ast.USub: operator.neg
}

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

def arithmetic_eval(s):
    binOps = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.Mod: operator.mod
    }

    unOps = {
    ast.USub: operator.neg
    }

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

    def _eval(node):
        if isinstance(node, ast.Expression):
            return _eval(node.body)
        elif isinstance(node, ast.Str):
            return node.s
        elif isinstance(node, ast.Num):
            return node.n
        elif isinstance(node, ast.BinOp):
            return binOps[type(node.op)](_eval(node.left), _eval(node.right))
        elif isinstance(node, ast.UnaryOp):
            return unOps[type(node.op)](_eval(node.operand))
        else:
            raise Exception('Unsupported type {}'.format(node))

    return _eval(node.body)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-18
    • 1970-01-01
    • 1970-01-01
    • 2013-01-14
    • 2019-01-13
    • 2016-09-06
    • 2013-12-05
    • 2020-10-10
    相关资源
    最近更新 更多