Подтвердить что ты не робот

ValueError: неверная строка при использовании ast.literal_eval

Широко известно, что использование eval() является потенциальным риском для безопасности, поэтому поощряется использование ast.literal_eval(node_or_string)

Однако в python 2.7 он возвращает ValueError: malformed string при запуске этого примера:

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

В то время как в python 3.3 этот пример работает так, как ожидалось:

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

Почему он запускается на python 3, а не на python 2? Как я могу исправить его в python 2.7 без использования рискованной функции eval()?

4b9b3361

Ответ 1

Причина, по которой это не работает в Python 2, заключается в ее реализации literal_eval. Первоначальная реализация выполняла только оценку числа для дополнений и вычитаний, когда правый операнд был комплексным числом. Это синтаксически необходимо для того, чтобы сложные числа были выражены как литералы.

Этот был изменен в Python 3, так что он поддерживает любое допустимое числовое выражение, которое должно быть с обеих сторон сложения и вычитания. Однако использование literal_eval по-прежнему ограничено дополнениями и вычитаниями.

Это главным образом потому, что literal_eval должна быть функцией, которая превращает один постоянный литерал (выраженный в виде строки) в объект Python. Вид как обратный repr для простых встроенных типов. Оценка фактического выражения не включена, и тот факт, что это работает с Python 3, является просто хорошим побочным эффектом от его реализации.

Чтобы оценить фактические выражения, не используя eval (чего мы не хотим), мы можем написать наш собственный алгоритм оценки выражения, который работает на AST. Это довольно просто, особенно для простых арифметических операций над числами (например, для создания собственного калькулятора и т.д.). Мы просто разбираем строку в AST и затем оцениваем полученное дерево, просматривая различные типы node и применяя правильную операцию.

Что-то вроде этого:

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()).

Ответ 2

Это для поддержки комплексных чисел (поскольку issue 4907). Например, 1 + 2j анализируется парсером как выражение, состоящее из целочисленного литерала, операции сложения и воображаемого литерала; но поскольку complex numbers являются встроенным типом, желательно, чтобы ast.literal_eval поддерживал синтаксис сложных чисел.

изменение в поведении между 2.x и 3.x заключается в том, чтобы поддерживать запись комплексного числа "неправильным способом", например. 1j + 2; тот факт, что он позволяет произвольные выражения сложения или вычитания, является (главным образом непреднамеренным) побочным эффектом.

Если вы хотите проанализировать произвольные арифметические выражения, вы должны разобрать синтаксическое дерево (используя ast.parse), подтвердите его с помощью белого списка, а затем оцените.

Ответ 3

Используйте источник, люк!

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

Здесь вы найдете свой ответ. В частности, версия 2.7 имеет странное ограничение на line 70, что правильный node 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_eval сложных литералов, например, чисел, таких как 9 + 0j, и никогда не собирался делать простые целочисленные дополнения. Затем в python 3 они активизировали literal_eval для обработки этих случаев.

Ответ 4

Нелегко использовать pyparsing, чтобы скомбинировать простой оценщик выражений.

Предположим, что вы хотите выразить выражение, включая parens, типа выражений следующего:

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

Это упрощение примера 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)))   

Печать

"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

Ответ 5

Обновленная версия ответа от @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)