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