web-dev-qa-db-ja.com

不正な文字列ValueError ast.literal_eval()とタプルの文字列表現

ファイルからタプルの文字列表現を読み取って、タプルをリストに追加しようとしています。関連するコードは次のとおりです。

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

ast.literal_evalast.pyにあります)は最初にast.parseでツリーを解析し、次に非常にquiteい再帰関数を使用してコードを評価し、解析ツリー要素を解釈してそれらをリテラルに置き換えます。残念ながら、コードはまったく拡張できないため、Decimalをコードに追加するには、すべてのコードをコピーして最初からやり直す必要があります。

少し簡単なアプローチとして、ast.parseモジュールを使用して式を解析し、ast.NodeVisitorまたはast.NodeTransformerを使用して、不要な構文や不要な変数アクセスがないことを確認できます。次に、compileevalを使用してコンパイルし、結果を取得します。

このコードは実際にevalを使用しているという点でliteral_evalとは少し異なりますが、私の意見では理解が簡単で、ASTツリー:ラムダ、属性アクセス(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)
35
Antti Haapala

documentation for ast.literal_eval()から:

式ノードまたはPython式を含む文字列を安全に評価します。 提供される文字列またはノードは、次のPythonリテラル構造のみで構成されます:文字列、数字、タプル、リスト、辞書、ブール値、およびなし。

Decimalは、ast.literal_eval()で許可されているもののリストにありません。

21
NPE

これは古い質問であることは知っていますが、誰かがそれを必要とする場合に備えて、非常に簡単な答えを見つけたと思います。

文字列の中に文字列引用符を入れると( "'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
0
nojco