字符串格式错误 ValueError ast.literal_eval() 与元组的字符串表示形式

56

我正在尝试从文件中读取一个元组的字符串表示,并将该元组添加到列表中。下面是相关代码:

raw_data = userfile.read().split('\n')
for a in raw_data : 
    print a
    btc_history.append(ast.literal_eval(a))

这是输出结果:

(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

1
如果是可信输入,你能评估它吗? - Jon Clements
这是我最初尝试的,但它给了我一个SyntaxError:解析时意外的EOF。这是可信的输入。 - Sinthet
3
这真让人烦恼... - Andy Hayden
你有跟进为什么会出现SyntaxError吗?通常情况下,eval()是没有人会建议你使用的,但既然它是可信输入,那将是实现你所需功能最简单的方法。 - hfaran
请查看我对类似问题的回答:https://dev59.com/yWUp5IYBdhLWcg3wj4Hu#68732605 - Jay M
5个回答

49

ast.literal_eval(位于ast.py中)首先使用ast.parse解析树,然后使用相当丑陋的递归函数评估代码,解释解析树元素并将其替换为它们的字面等价物。不幸的是,该代码根本不可扩展,因此要向代码添加Decimal,必须复制所有代码并重新开始。

对于稍微简单的方法,您可以使用ast.parse模块解析表达式,然后使用ast.NodeVisitorast.NodeTransformer确保没有不需要的语法或不需要的变量访问。然后使用compileeval进行编译以获得结果。

literal_eval略有不同的是,此代码实际上使用了eval,但我认为它更容易理解,而且不需要深入挖掘AST树。它特别只允许一些语法,明确禁止例如lambdas、属性访问(foo.__dict__非常危险)或对任何未被视为安全的名称的访问。它可以很好地解析您的表达式,并且作为额外补充,我还添加了Num(浮点数和整数)、列表和字典文字。

此外,在2.7和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)

1
请注意,在Python 3.4及以上版本中,有一种新的节点类型用于None、False和True等。 - Antti Haapala -- Слава Україні

34

根据 ast.literal_eval()文档

安全地计算表达式节点或包含Python表达式的字符串。提供的字符串或节点只能包含以下Python字面结构:字符串、数字、元组、列表、字典、布尔值和None。

Decimal 不在 ast.literal_eval() 允许的列表中。


我找到了一个迂回的方法来解决我之前设置赏金的问题,但我希望看到其他人特别是 NPE 提供一个更高质量的解决方案,因为他已经在这个主题中具有某些权威性。 - jdero
@jdero:如果你面对不受信任的输入,唯一安全的解决方案是创建一个自定义解析器来处理输入字符串。 - NPE
@NPE 一个自定义解析器会是什么样子?如果您在答案中提供更多详细信息,我将很乐意接受它。 - jdero
实际上,您不需要自定义解析器,只需使用Python解析器,然后解释AST树即可。 - Antti Haapala -- Слава Україні
@AnttiHaapala 如果您想提供另一个更详细的答案,我很乐意接受。 - jdero
显示剩余2条评论

7

在我的情况下,我使用以下方法解决了问题:

my_string = my_string.replace(':false', ':False').replace(':true', ':True')
ast.literal_eval(my_string)

我需要在冒号后面加上空格:my_string = my_string.replace(': false', ': False').replace(': true', ': True')在从Python 3.6迁移到Python 3.10之后(解析Django 4的queryset.explain(format='JSON'))。 - greg

4
如果输入内容是可信的(在您的情况下是这样),请使用eval()而不是ast.literal_eval()
raw_data = userfile.read().split('\n')
for a in raw_data : 
    print a
    btc_history.append(eval(a))

这对我来说在Python 3.6.0下有效。


2
我知道这是一个老问题,但我认为找到了一个非常简单的答案,以防有人需要。
如果您在字符串内放置字符串引号("'hello'"),ast_literaleval()会完美理解它。
您可以使用一个简单的函数:
"最初的回答"
    def doubleStringify(a):
        b = "\'" + a + "\'"
        return b

或许更适合这个例子的是:

    def perfectEval(anonstring):
        try:
            ev = ast.literal_eval(anonstring)
            return ev
        except ValueError:
            corrected = "\'" + anonstring + "\'"
            ev = ast.literal_eval(corrected)
            return ev

这只是将其评估为字符串。看看repr(ev)。 - Alcamtar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接