make - parser library python
Malformed String ValueError ast.literal_eval() con representación en cadena de Tuple (2)
Intento leer en una representación de cadena de un Tuple desde un archivo y agregar la tupla a una lista. Aquí está el código relevante.
raw_data = userfile.read().split(''/n'')
for a in raw_data :
print a
btc_history.append(ast.literal_eval(a))
Aquí está el resultado:
(Decimal(''11.66985''), Decimal(''0E-8''))
Traceback (most recent call last):
File "./goxnotify.py", line 74, in <module>
main()
File "./goxnotify.py", line 68, in main
local.load_user_file(username,btc_history)
File "/home/unix-dude/Code/GoxNotify/local_functions.py", line 53, in load_user_file
btc_history.append(ast.literal_eval(a))
File "/usr/lib/python2.7/ast.py", line 80, in literal_eval
return _convert(node_or_string)
`File "/usr/lib/python2.7/ast.py", line 58, in _convert
return tuple(map(_convert, node.elts))
File "/usr/lib/python2.7/ast.py", line 79, in _convert
raise ValueError(''malformed string'')
ValueError: malformed string
De la documentation para ast.literal_eval()
:
Evaluar de forma segura un nodo de expresión o una cadena que contenga una expresión de Python. La cadena o nodo proporcionado solo puede consistir en las siguientes estructuras literales de Python: cadenas, números, tuplas, listas, dictados, booleanos y Ninguno.
Decimal
no está en la lista de cosas permitidas por ast.literal_eval()
.
ast.literal_eval
(ubicado en ast.py
) analiza el árbol con ast.parse
primero, luego evalúa el código con una función recursiva bastante fea, interpretando los elementos del árbol de análisis y reemplazándolos con sus equivalentes literales. Lamentablemente, el código no es expansible en absoluto, así que para agregar un código Decimal
al código, debe copiar todo el código y comenzar de nuevo.
Para un enfoque un poco más fácil, puede usar el módulo ast.parse
para analizar la expresión, y luego el ast.NodeVisitor
o ast.NodeTransformer
para asegurarse de que no haya una sintaxis no deseada o accesos a variables no deseadas. Luego compila con compile
y eval
para obtener el resultado.
El código es un poco diferente de literal_eval
porque este código realmente usa eval
, pero en mi opinión es más simple de entender y no es necesario cavar demasiado profundo en los árboles AST. Específicamente solo permite alguna sintaxis, que prohíbe explícitamente, por ejemplo, lambdas, acceso a atributos ( foo.__dict__
es muy malo) o acceso a nombres que no se consideran seguros. Analiza bien su expresión, y como un extra también agregué Num
(flotante y entero), lista y literales de diccionario.
Además, funciona igual en 2.7 y 3.3
import ast
import decimal
source = "(Decimal(''11.66985''), Decimal(''1e-8''),"/
"(1,), (1,2,3), 1.2, [1,2,3], {1:2})"
tree = ast.parse(source, mode=''eval'')
# using the NodeTransformer, you can also modify the nodes in the tree,
# however in this example NodeVisitor could do as we are raising exceptions
# only.
class Transformer(ast.NodeTransformer):
ALLOWED_NAMES = set([''Decimal'', ''None'', ''False'', ''True''])
ALLOWED_NODE_TYPES = set([
''Expression'', # a top node for an expression
''Tuple'', # makes a tuple
''Call'', # a function call (hint, Decimal())
''Name'', # an identifier...
''Load'', # loads a value of a variable with given identifier
''Str'', # a string literal
''Num'', # allow numbers too
''List'', # and list literals
''Dict'', # and dicts...
])
def visit_Name(self, node):
if not node.id in self.ALLOWED_NAMES:
raise RuntimeError("Name access to %s is not allowed" % node.id)
# traverse to child nodes
return self.generic_visit(node)
def generic_visit(self, node):
nodetype = type(node).__name__
if nodetype not in self.ALLOWED_NODE_TYPES:
raise RuntimeError("Invalid expression: %s not allowed" % nodetype)
return ast.NodeTransformer.generic_visit(self, node)
transformer = Transformer()
# raises RuntimeError on invalid code
transformer.visit(tree)
# compile the ast into a code object
clause = compile(tree, ''<AST>'', ''eval'')
# make the globals contain only the Decimal class,
# and eval the compiled object
result = eval(clause, dict(Decimal=decimal.Decimal))
print(result)