ValueError: cadena con formato incorrecto al utilizar ast.literal_eval

Es ampliamente conocido que usar eval() es un riesgo potencial de seguridad, por lo que se ast.literal_eval(node_or_string) el uso de ast.literal_eval(node_or_string)

Sin embargo, en Python 2.7 devuelve ValueError: malformed string al ejecutar este ejemplo:

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

Mientras que en Python 3.3 este ejemplo funciona como se espera:

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

¿Por qué se ejecuta en python 3 y no en python 2? ¿Cómo puedo solucionarlo en Python 2.7 sin usar la función eval() arriesgada?

Related of "ValueError: cadena con formato incorrecto al utilizar ast.literal_eval"

La razón por la que esto no funciona en Python 2 radica en su implementación de literal_eval . La implementación original solo realizó la evaluación de números para sums y restas cuando el operando derecho era un número complejo. Esto es sintácticamente necesario para que los números complejos se expresen como un literal.

Esto se cambió en Python 3 para que sea compatible con cualquier tipo de expresión numérica válida que esté a ambos lados de la sum y la resta. Sin embargo, el uso de literal_eval todavía está restringido a sums y restas.

Esto se debe principalmente a que literal_eval se supone que es una función que convierte un único literal constante (expresado como una cadena) en un objeto Python. Algo así como una repr hacia atrás para tipos incorporados simples. La evaluación de la expresión real no está incluida, y el hecho de que funcione con Python 3 es solo un efecto secundario agradable de su implementación.

Para evaluar las expresiones reales, sin tener que usar eval (que no queremos), podemos escribir nuestro propio algoritmo de evaluación de expresiones que opera en el AST. Esto es bastante simple, especialmente para operaciones aritméticas simples en números (por ejemplo, para construir su propia calculadora, etc.). Simplemente analizamos la cadena en un AST y luego evaluamos el árbol resultante observando los diferentes tipos de nodos y aplicando la operación correcta.

Algo como esto:

 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) 

Como puede ver, esta implementación es bastante sencilla. Por supuesto, aún no admite cosas más complejas como la exponenciación y algunos nodos unarios, pero no es demasiado difícil agregar eso. Y funciona bien:

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

Incluso podría introducir cosas más complejas más adelante (por ejemplo, la función requiere cosas como sin() ).

Es para soportar números complejos (desde el número 4907 ). Por ejemplo, el analizador analiza 1 + 2j como una expresión que consiste en un literal entero, una operación de sum y un literal imaginario ; pero como los números complejos son un tipo integrado, es conveniente que ast.literal_eval admita la syntax de números complejos.

El cambio en el comportamiento entre 2.xy 3.x es para admitir la escritura del número complejo “a la inversa”, por ejemplo, 1j + 2 ; el hecho de que permita expresiones de sum o resta arbitrarias es un efecto secundario (en su mayoría no intencionado).

Si desea analizar expresiones aritméticas arbitrarias, debe analizar un árbol de syntax (utilizando ast.parse ), verificarlo con una lista blanca y luego evaluar.

Usa la fuente, luke!

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

Encontrarás tu respuesta allí. Específicamente, la versión 2.7 tiene la extraña restricción en la línea 70 de que el nodo derecho de BinOp es complejo.

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

Supongo que la intención de 2.7 era permitir un literal_eval de literales complejos para números de ejemplo como 9 + 0j , y nunca fue la intención de hacer sums de enteros simples. Luego, en Python 3, reforzaron el literal_eval para manejar estos casos.

No es demasiado difícil usar el uso de pyparsing para improvisar un evaluador de expresión simple.

Supongamos que desea evaluar la expresión, incluidos los parens, del tipo de expresiones de los siguientes:

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

Esta simplificación del ejemplo 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))) 

Huellas dactilares:

 "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