Python ValueError:使用ast.literal\u eval时字符串格式不正确
众所周知,使用Python ValueError:使用ast.literal\u eval时字符串格式不正确,python,python-2.7,python-3.x,Python,Python 2.7,Python 3.x,众所周知,使用eval()是一种潜在的安全风险,因此提倡使用 但是,在python 2.7中,当运行此示例时,它返回ValueError:malformed string: >>> ast.literal_eval("4 + 9") 而在python 3.3中,此示例按预期工作: >>> ast.literal_eval('4+9') 13 为什么它运行在Python3而不是Python2上?如何在python 2.7中修复它而不使用高风险的eval()函
eval()
是一种潜在的安全风险,因此提倡使用
但是,在python 2.7中,当运行此示例时,它返回ValueError:malformed string
:
>>> ast.literal_eval("4 + 9")
而在python 3.3中,此示例按预期工作:
>>> ast.literal_eval('4+9')
13
为什么它运行在Python3而不是Python2上?如何在python 2.7中修复它而不使用高风险的
eval()
函数?这在python 2上不起作用的原因在于它实现了literal\u eval
。当右操作数是复数时,原始实现仅对加法和减法执行数字求值。这在语法上对于复数表示为文字是必要的
这在Python3中得到了应用,因此它支持任何类型的有效数字表达式位于加法和减法的任意一侧。然而,literal\u eval
的使用仍然局限于加法和减法
这主要是因为literal\u eval
应该是一个将单个常量文本(表示为字符串)转换为Python对象的函数。对于简单的内置类型,有点像向后的repr
。不包括实际的表达式求值,而这一点在Python3中也适用,这只是其实现的一个很好的副作用
为了计算实际表达式,而不必使用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()
之类的内容的函数调用)。使用源代码,luke
你会在那里找到你的答案。具体来说,2.7版本有一个奇怪的限制,即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\u eval
,例如9+0j
之类的数字,它从来没有打算进行简单的整数加法。然后在Python3中,他们增强了literal\u eval
来处理这些情况 这是为了支持复数(因为)。例如,1+2j
被解析器解析为一个表达式,该表达式由整数文本、加法运算和;但由于是内置类型,因此,ast.literal\u eval
需要支持复数语法
介于2.x和3.x之间的函数用于支持以“错误的方式”写入复数,例如1j+2
;它允许任意的加法或减法表达式,这是一个(大部分是无意的)副作用
如果要解析任意算术表达式,应该解析到语法树(使用),然后求值。拼凑一个简单的表达式求值器并不难
假设要计算表达式(包括参数)的以下类型的表达式:
2+3
4.0^2+5*(2+3+4)
1.23+4.56-7.890
(1+2+3+4)/5
1e6^2/1e7
该示例的简化如下:
@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)
这绝对不是字面意思,所以我认为Python 3.3在这里是错误的。也许这是窥视孔优化的意外结果?编辑:不,它是特殊的大小写和常量折叠在
literal\u eval
中。不过,只针对加法和减法,没有乘法或除法或其他运算符。另请参见2.7中添加的奇怪行为,它似乎是为了解决这个问题。最让我困惑的是,为什么开发人员没有将python 3的ast.literal\u eval
实现到python 2中?因为2+3不是一个literal。如果我用monkeypatch+
做一些不同的事情会怎么样?我同意这里的2.7行为。在我看来,工作只是处理复数的一个丑陋的副作用。如果支持加法,为什么不除法、乘法或?事实上,这在3.6中最终被认为是一个bug,现在在3.7中得到了修复+1、感谢您提供有关复数的信息!没有想到加法是将它们作为文本所必需的语法的一部分。+1有趣的解决方案。。。使用pyparser
的优点是什么?它是额外安全的还是更快的?主要优点是对输入和输出的更多控制。使用解析器与使用AST literal_eval一样安全。它将验证您的输入是否符合您的期望,并且只执行您告诉它的内容。这是一个伟大的工具。AST也是一个很棒的工具。我认为在python3中,operator.div被删除了。您可以改用operator.truediv。
"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
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)