ValueError:使用ast.literal_eval时格式错误的字符串

时间:2013-12-23 17:22:20

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

众所周知,使用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中修复它?

5 个答案:

答案 0 :(得分:31)

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

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

这主要是因为literal_eval应该是一个将单个常量文字(表示为字符串)转换为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()之类的函数调用。)

答案 1 :(得分:6)

这是为了支持复数(从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,然后进行求值。

答案 2 :(得分: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来处理这些案例。

答案 3 :(得分:3)

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

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

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

答案 4 :(得分:0)

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

<VirtualHost *:443>
    ServerName ${HTTPS_SERVER_NAME_PORT}

    SSLEngine on
    SSLCertificateFile "/usr/local/apache2/ssl/cert.pem"
    SSLCertificateKeyFile "/usr/local/apache2/ssl/key.pem"
    SSLCertificateChainFile "/usr/local/apache2/ssl/cacert.pem"
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

    <Location "/service">
        ProxyPass http://server:8081/
        ProxyPassReverse http://server:8081/

        ProxyPreserveHost On
        # AuthType Basic
        RequestHeader unset Authorization
        SetEnv proxy-chain-auth On
    </Location>
</VirtualHost>