python-2.7 - ValueError: 使用 ast.literal_eval 时字符串格式不正确

众所周知,使用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上不能运行? 如何在python 2.7中修复它,而不用使用危险的eval()函数?

时间:

在Python 3中对此进行了更改,使它在加法和减法的任何一侧都支持任何一种有效的数字表达式,然而,literal_eval的使用仍然局限于增加和减少。

我现在没有理想的解决方案,不涉及eval,但是如果表达式简单,则如下所示,只需对数字(创建自己的计算器等等)进行算术运算,就可以在这里编写自己的评估算法,方法是检查不同的node类型并应用正确的操作。

像这样:


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

它是为了支持复数( 因为问题4907 ) 。 例如, 1 + 2j解析xml解析器作为表达式组成的整数文本,一个加法运算和一个虚数字面值,但由于复数是内建类型,期望模式的未来 ast.literal_eval以支持复数语法。

在行为更改介于2 。x 和3 。x 是支持编写的复数" 错误的办法可以找到" e .g 。 1j + 2,事实上,它允许任意的加法或减法表达式是( 主要是意外的) 副作用。

如果要解析任意算术表达式,应该解析为语法树( 使用ast.parse),验证跟白名单,然后评估。?

使用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

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


>>> 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

在python 3中,加强了literal_eval来处理这些情况。

...