Avaliando uma expressão matemática em uma string

113
stringExp = "2^4"
intVal = int(stringExp)      # Expected value: 16

Isso retorna o seguinte erro:

Traceback (most recent call last):  
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'

Eu sei que isso evalpode contornar isso, mas não existe um método melhor e - mais importante - mais seguro para avaliar uma expressão matemática que está sendo armazenada em uma string?

Pieter
fonte
6
^ é o operador XOR. O valor esperado é 6. Você provavelmente deseja pow (2,4).
kgiannakakis
25
ou mais pitonicamente 2 ** 4
fortran
1
Se você não quiser usar eval, a única solução é implementar o analisador gramatical apropriado. Dê uma olhada em pyparsing .
kgiannakakis

Respostas:

108

Pyparsing pode ser usado para analisar expressões matemáticas. Em particular, fourFn.py mostra como analisar expressões aritméticas básicas. Abaixo, reenviei fourFn em uma classe de analisador numérico para facilitar a reutilização.

from __future__ import division
from pyparsing import (Literal, CaselessLiteral, Word, Combine, Group, Optional,
                       ZeroOrMore, Forward, nums, alphas, oneOf)
import math
import operator

__author__ = 'Paul McGuire'
__version__ = '$Revision: 0.0 $'
__date__ = '$Date: 2009-03-20 $'
__source__ = '''http://pyparsing.wikispaces.com/file/view/fourFn.py
http://pyparsing.wikispaces.com/message/view/home/15549426
'''
__note__ = '''
All I've done is rewrap Paul McGuire's fourFn.py as a class, so I can use it
more easily in other places.
'''


class NumericStringParser(object):
    '''
    Most of this code comes from the fourFn.py pyparsing example

    '''

    def pushFirst(self, strg, loc, toks):
        self.exprStack.append(toks[0])

    def pushUMinus(self, strg, loc, toks):
        if toks and toks[0] == '-':
            self.exprStack.append('unary -')

    def __init__(self):
        """
        expop   :: '^'
        multop  :: '*' | '/'
        addop   :: '+' | '-'
        integer :: ['+' | '-'] '0'..'9'+
        atom    :: PI | E | real | fn '(' expr ')' | '(' expr ')'
        factor  :: atom [ expop factor ]*
        term    :: factor [ multop factor ]*
        expr    :: term [ addop term ]*
        """
        point = Literal(".")
        e = CaselessLiteral("E")
        fnumber = Combine(Word("+-" + nums, nums) +
                          Optional(point + Optional(Word(nums))) +
                          Optional(e + Word("+-" + nums, nums)))
        ident = Word(alphas, alphas + nums + "_$")
        plus = Literal("+")
        minus = Literal("-")
        mult = Literal("*")
        div = Literal("/")
        lpar = Literal("(").suppress()
        rpar = Literal(")").suppress()
        addop = plus | minus
        multop = mult | div
        expop = Literal("^")
        pi = CaselessLiteral("PI")
        expr = Forward()
        atom = ((Optional(oneOf("- +")) +
                 (ident + lpar + expr + rpar | pi | e | fnumber).setParseAction(self.pushFirst))
                | Optional(oneOf("- +")) + Group(lpar + expr + rpar)
                ).setParseAction(self.pushUMinus)
        # by defining exponentiation as "atom [ ^ factor ]..." instead of
        # "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
        # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
        factor = Forward()
        factor << atom + \
            ZeroOrMore((expop + factor).setParseAction(self.pushFirst))
        term = factor + \
            ZeroOrMore((multop + factor).setParseAction(self.pushFirst))
        expr << term + \
            ZeroOrMore((addop + term).setParseAction(self.pushFirst))
        # addop_term = ( addop + term ).setParseAction( self.pushFirst )
        # general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
        # expr <<  general_term
        self.bnf = expr
        # map operator symbols to corresponding arithmetic operations
        epsilon = 1e-12
        self.opn = {"+": operator.add,
                    "-": operator.sub,
                    "*": operator.mul,
                    "/": operator.truediv,
                    "^": operator.pow}
        self.fn = {"sin": math.sin,
                   "cos": math.cos,
                   "tan": math.tan,
                   "exp": math.exp,
                   "abs": abs,
                   "trunc": lambda a: int(a),
                   "round": round,
                   "sgn": lambda a: abs(a) > epsilon and cmp(a, 0) or 0}

    def evaluateStack(self, s):
        op = s.pop()
        if op == 'unary -':
            return -self.evaluateStack(s)
        if op in "+-*/^":
            op2 = self.evaluateStack(s)
            op1 = self.evaluateStack(s)
            return self.opn[op](op1, op2)
        elif op == "PI":
            return math.pi  # 3.1415926535
        elif op == "E":
            return math.e  # 2.718281828
        elif op in self.fn:
            return self.fn[op](self.evaluateStack(s))
        elif op[0].isalpha():
            return 0
        else:
            return float(op)

    def eval(self, num_string, parseAll=True):
        self.exprStack = []
        results = self.bnf.parseString(num_string, parseAll)
        val = self.evaluateStack(self.exprStack[:])
        return val

Você pode usar assim

nsp = NumericStringParser()
result = nsp.eval('2^4')
print(result)
# 16.0

result = nsp.eval('exp(2^4)')
print(result)
# 8886110.520507872
unutbu
fonte
180

eval é mau

eval("__import__('os').remove('important file')") # arbitrary commands
eval("9**9**9**9**9**9**9**9", {'__builtins__': None}) # CPU, memory

Observação: mesmo se você usar definido __builtins__para None, ainda pode ser possível quebrar usando introspecção:

eval('(1).__class__.__bases__[0].__subclasses__()', {'__builtins__': None})

Avalie a expressão aritmética usando ast

import ast
import operator as op

# supported operators
operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
             ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor,
             ast.USub: op.neg}

def eval_expr(expr):
    """
    >>> eval_expr('2^6')
    4
    >>> eval_expr('2**6')
    64
    >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
    -5.0
    """
    return eval_(ast.parse(expr, mode='eval').body)

def eval_(node):
    if isinstance(node, ast.Num): # <number>
        return node.n
    elif isinstance(node, ast.BinOp): # <left> <operator> <right>
        return operators[type(node.op)](eval_(node.left), eval_(node.right))
    elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
        return operators[type(node.op)](eval_(node.operand))
    else:
        raise TypeError(node)

Você pode facilmente limitar o intervalo permitido para cada operação ou qualquer resultado intermediário, por exemplo, para limitar os argumentos de entrada para a**b:

def power(a, b):
    if any(abs(n) > 100 for n in [a, b]):
        raise ValueError((a,b))
    return op.pow(a, b)
operators[ast.Pow] = power

Ou para limitar a magnitude dos resultados intermediários:

import functools

def limit(max_=None):
    """Return decorator that limits allowed returned values."""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            ret = func(*args, **kwargs)
            try:
                mag = abs(ret)
            except TypeError:
                pass # not applicable
            else:
                if mag > max_:
                    raise ValueError(ret)
            return ret
        return wrapper
    return decorator

eval_ = limit(max_=10**100)(eval_)

Exemplo

>>> evil = "__import__('os').remove('important file')"
>>> eval_expr(evil) #doctest:+IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError:
>>> eval_expr("9**9")
387420489
>>> eval_expr("9**9**9**9**9**9**9**9") #doctest:+IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError:
jfs
fonte
29
Postagem muito legal, obrigado. Peguei esse conceito e tentei fazer uma biblioteca que fosse fácil de usar: github.com/danthedeckie/simpleeval
Daniel Fairhead
isso pode ser estendido para funções de import math?
Hotschke
2
Observe que ast.parsenão é seguro. Por exemplo, ast.parse('()' * 1000000, '<string>', 'single')trava o interpretador.
Antti Haapala
1
@AnttiHaapala bom exemplo. É um bug no interpretador Python? De qualquer forma, uma entrada grande é tratada trivialmente, por exemplo, usando if len(expr) > 10000: raise ValueError.
jfs
1
@AnttiHaapala você poderia fornecer um exemplo que não pode ser corrigido usando o len(expr)cheque? Ou seu ponto é que existem bugs na implementação do Python e, portanto, é impossível escrever código seguro em geral?
jfs
13

Algumas alternativas mais seguras para eval()e * :sympy.sympify().evalf()

* SymPy sympifytambém não é seguro de acordo com o seguinte aviso da documentação.

Aviso: Observe que esta função usa evale, portanto, não deve ser usada em entradas não higienizadas.

Mark Mikofski
fonte
10

Ok, então o problema com eval é que ele pode escapar de sua sandbox com muita facilidade, mesmo se você se livrar dela __builtins__. Todos os métodos para escapar da sandbox se resumem em usar getattrou object.__getattribute__(por meio do .operador) para obter uma referência a algum objeto perigoso por meio de algum objeto permitido ( ''.__class__.__bases__[0].__subclasses__ou semelhante). getattré eliminado configurando __builtins__para None. object.__getattribute__é o difícil, pois não pode simplesmente ser removido, tanto porque objecté imutável quanto porque removê-lo quebraria tudo. No entanto, __getattribute__só é acessível por meio do .operador, portanto, purgá-lo de sua entrada é suficiente para garantir que eval não escape de sua sandbox.
Em fórmulas de processamento, o único uso válido de um decimal é quando ele é precedido ou seguido por[0-9], então apenas removemos todas as outras instâncias de ..

import re
inp = re.sub(r"\.(?![0-9])","", inp)
val = eval(inp, {'__builtins__':None})

Observe que, embora o python normalmente trate 1 + 1.como 1 + 1.0, isso removerá o rastro .e deixará você com 1 + 1. Você poderia adicionar ), e EOFà lista de coisas que podem ser seguidas ., mas por que se preocupar?

Perkins
fonte
Uma questão relacionada com discussão interessante pode ser encontrada aqui .
djvg
3
Quer o argumento sobre a remoção .esteja correto ou não no momento, isso deixa o potencial para vulnerabilidades de segurança se versões futuras do Python introduzirem uma nova sintaxe, permitindo que objetos ou funções inseguros sejam acessados ​​de outra forma. Esta solução já não é seguro em Python 3,6 por causa da f-cadeias, que permitem que o ataque seguinte: f"{eval('()' + chr(46) + '__class__')}". Uma solução baseada em whitelisting em vez de blacklisting será mais segura, mas realmente é melhor resolver este problema sem evalnada.
kaya3
Esse é um ponto excelente sobre recursos de linguagem futuros que apresentam novos problemas de segurança.
Perkins
8

Você pode usar o módulo ast e escrever um NodeVisitor que verifica se o tipo de cada nó faz parte de uma lista de permissões.

import ast, math

locals =  {key: value for (key,value) in vars(math).items() if key[0] != '_'}
locals.update({"abs": abs, "complex": complex, "min": min, "max": max, "pow": pow, "round": round})

class Visitor(ast.NodeVisitor):
    def visit(self, node):
       if not isinstance(node, self.whitelist):
           raise ValueError(node)
       return super().visit(node)

    whitelist = (ast.Module, ast.Expr, ast.Load, ast.Expression, ast.Add, ast.Sub, ast.UnaryOp, ast.Num, ast.BinOp,
            ast.Mult, ast.Div, ast.Pow, ast.BitOr, ast.BitAnd, ast.BitXor, ast.USub, ast.UAdd, ast.FloorDiv, ast.Mod,
            ast.LShift, ast.RShift, ast.Invert, ast.Call, ast.Name)

def evaluate(expr, locals = {}):
    if any(elem in expr for elem in '\n#') : raise ValueError(expr)
    try:
        node = ast.parse(expr.strip(), mode='eval')
        Visitor().visit(node)
        return eval(compile(node, "<string>", "eval"), {'__builtins__': None}, locals)
    except Exception: raise ValueError(expr)

Como funciona por meio de uma lista de permissões em vez de uma lista negra, é seguro. As únicas funções e variáveis ​​que ele pode acessar são aquelas às quais você explicitamente concede acesso. Preenchi um dicionário com funções relacionadas à matemática para que você possa fornecer acesso facilmente a elas, se quiser, mas é necessário usá-lo explicitamente.

Se a string tentar chamar funções que não foram fornecidas, ou invocar qualquer método, uma exceção será levantada e ela não será executada.

Como ele usa o analisador e o avaliador embutido do Python, ele também herda as regras de promoção e precedência do Python.

>>> evaluate("7 + 9 * (2 << 2)")
79
>>> evaluate("6 // 2 + 0.0")
3.0

O código acima foi testado apenas no Python 3.

Se desejar, você pode adicionar um decorador de tempo limite nesta função.

Kevin
fonte
7

O motivo evale execsão tão perigosos é que a compilefunção padrão irá gerar bytecode para qualquer expressão Python válida, e o padrão evalou execirá executar qualquer bytecode Python válido. Todas as respostas até agora se concentraram em restringir o bytecode que pode ser gerado (limpando a entrada) ou construindo sua própria linguagem de domínio específico usando o AST.

Em vez disso, você pode criar facilmente uma evalfunção simples que é incapaz de fazer qualquer coisa nefasta e pode facilmente ter verificações de tempo de execução na memória ou no tempo usado. Claro, se for matemática simples, então existe um atalho.

c = compile(stringExp, 'userinput', 'eval')
if c.co_code[0]==b'd' and c.co_code[3]==b'S':
    return c.co_consts[ord(c.co_code[1])+ord(c.co_code[2])*256]

A maneira como isso funciona é simples, qualquer expressão matemática constante é avaliada com segurança durante a compilação e armazenada como uma constante. O objeto de código retornado por compile consiste em d, que é o bytecode para LOAD_CONST, seguido pelo número da constante a ser carregada (geralmente a última na lista), seguido por S, que é o bytecode para RETURN_VALUE. Se este atalho não funcionar, significa que a entrada do usuário não é uma expressão constante (contém uma variável ou chamada de função ou semelhante).

Isso também abre a porta para alguns formatos de entrada mais sofisticados. Por exemplo:

stringExp = "1 + cos(2)"

Isso requer uma avaliação real do bytecode, que ainda é bastante simples. O bytecode Python é uma linguagem orientada a pilha, então tudo é uma questão simples TOS=stack.pop(); op(TOS); stack.put(TOS)ou semelhante. A chave é implementar apenas os opcodes que são seguros (carregar / armazenar valores, operações matemáticas, valores de retorno) e não os inseguros (pesquisa de atributo). Se você deseja que o usuário seja capaz de chamar funções (toda a razão para não usar o atalho acima), simplesmente faça sua implementação de CALL_FUNCTIONpermitir apenas funções em uma lista 'segura'.

from dis import opmap
from Queue import LifoQueue
from math import sin,cos
import operator

globs = {'sin':sin, 'cos':cos}
safe = globs.values()

stack = LifoQueue()

class BINARY(object):
    def __init__(self, operator):
        self.op=operator
    def __call__(self, context):
        stack.put(self.op(stack.get(),stack.get()))

class UNARY(object):
    def __init__(self, operator):
        self.op=operator
    def __call__(self, context):
        stack.put(self.op(stack.get()))


def CALL_FUNCTION(context, arg):
    argc = arg[0]+arg[1]*256
    args = [stack.get() for i in range(argc)]
    func = stack.get()
    if func not in safe:
        raise TypeError("Function %r now allowed"%func)
    stack.put(func(*args))

def LOAD_CONST(context, arg):
    cons = arg[0]+arg[1]*256
    stack.put(context['code'].co_consts[cons])

def LOAD_NAME(context, arg):
    name_num = arg[0]+arg[1]*256
    name = context['code'].co_names[name_num]
    if name in context['locals']:
        stack.put(context['locals'][name])
    else:
        stack.put(context['globals'][name])

def RETURN_VALUE(context):
    return stack.get()

opfuncs = {
    opmap['BINARY_ADD']: BINARY(operator.add),
    opmap['UNARY_INVERT']: UNARY(operator.invert),
    opmap['CALL_FUNCTION']: CALL_FUNCTION,
    opmap['LOAD_CONST']: LOAD_CONST,
    opmap['LOAD_NAME']: LOAD_NAME
    opmap['RETURN_VALUE']: RETURN_VALUE,
}

def VMeval(c):
    context = dict(locals={}, globals=globs, code=c)
    bci = iter(c.co_code)
    for bytecode in bci:
        func = opfuncs[ord(bytecode)]
        if func.func_code.co_argcount==1:
            ret = func(context)
        else:
            args = ord(bci.next()), ord(bci.next())
            ret = func(context, args)
        if ret:
            return ret

def evaluate(expr):
    return VMeval(compile(expr, 'userinput', 'eval'))

Obviamente, a versão real disso seria um pouco mais longa (há 119 opcodes, 24 dos quais relacionados à matemática). Adicionar STORE_FASTe alguns outros permitiria uma entrada semelhante 'x=5;return x+xou semelhante, com uma facilidade trivial. Ele pode até mesmo ser usado para executar funções criadas pelo usuário, desde que as funções criadas pelo usuário sejam executadas via VMeval (não as torne chamáveis ​​!!! ou elas poderiam ser usadas como um retorno de chamada em algum lugar). O tratamento de loops requer suporte para os gotobytecodes, o que significa mudar de um foriterador para whilee manter um ponteiro para a instrução atual, mas não é muito difícil. Para resistência ao DOS, o loop principal deve verificar quanto tempo se passou desde o início do cálculo, e certos operadores devem negar a entrada acima de algum limite razoável (BINARY_POWER sendo o mais óbvio).

Embora essa abordagem seja um pouco mais longa do que um analisador gramatical simples para expressões simples (veja acima sobre apenas pegar a constante compilada), ela se estende facilmente para entradas mais complicadas e não requer lidar com gramática ( compilepegue qualquer coisa arbitrariamente complicada e a reduz para uma sequência de instruções simples).

Perkins
fonte
6

Acho que usaria eval(), mas primeiro verificaria se a string é uma expressão matemática válida, ao invés de algo malicioso. Você pode usar um regex para a validação.

eval() também aceita argumentos adicionais que você pode usar para restringir o namespace em que opera para maior segurança.

Tim Goodman
fonte
3
Mas, é claro, não confie em expressões regulares para validar expressões matemáticas arbitrárias.
Marca de alto desempenho
@ Mark de alto desempenho: Sim, acho que depende do tipo de expressões matemáticas que ele tem em mente. . . por exemplo, apenas aritmética simples com números e +, -, *, /, **, (, )ou algo mais complicado
Tim Goodman
@Tim - é o () que me preocupa, ou melhor, o (((((())))))). Na verdade, acho que OP deveria se preocupar com eles, minha testa não está enrugada pelos problemas de OP.
Marca de alto desempenho
2
Não use eval()se você não controlar a entrada, mesmo se restringir o namespace, por exemplo, eval("9**9**9**9**9**9**9**9", {'__builtins__': None})consome CPU, memória.
jfs
3
Restringir o namespace de eval não adiciona segurança .
Antti Haapala,
5

Esta é uma resposta extremamente tardia, mas acho útil para referência futura. Em vez de escrever seu próprio analisador matemático (embora o exemplo de análise acima seja excelente), você pode usar o SymPy. Não tenho muita experiência com ele, mas contém um mecanismo matemático muito mais poderoso do que qualquer pessoa possa escrever para um aplicativo específico e a avaliação de expressão básica é muito fácil:

>>> import sympy
>>> x, y, z = sympy.symbols('x y z')
>>> sympy.sympify("x**3 + sin(y)").evalf(subs={x:1, y:-3})
0.858879991940133

Muito legal mesmo! A from sympy import *traz muito mais suporte a funções, como funções trigonométricas, funções especiais etc., mas evitei isso aqui para mostrar o que está vindo de onde.

andybuckley
fonte
3
Sympy é "seguro"? Parece haver vários posts que sugerem que é um invólucro em torno de eval () que pode ser explorado da mesma maneira. Também evalfnão aceita ndarrays entorpecidos.
Mark Mikofski
14
Nenhum sympy não é seguro para entrada não confiável. Tente sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")este chamadas subprocess.Popen()que passei lsem vez de rm -rf /. O índice provavelmente será diferente em outros computadores. Esta é uma variante da exploração
Mark Mikofski
1
Na verdade, não acrescenta nada à segurança.
Antti Haapala
4

[Sei que essa é uma pergunta antiga, mas vale a pena apontar novas soluções úteis à medida que aparecem]

Desde o python3.6, esse recurso agora é integrado à linguagem , denominada "f-strings" .

Veja: PEP 498 - Interpolação de String Literal

Por exemplo (observe o fprefixo):

f'{2**4}'
=> '16'
shx2
fonte
7
Link muito interessante. Mas acho que f-strings estão aqui para tornar a escrita do código-fonte mais fácil, enquanto a questão parece ser sobre como trabalhar com strings dentro de variáveis ​​(possivelmente de fontes não confiáveis). strings f não podem ser usadas nesse caso.
Bernhard
existe alguma maneira de fazer algo no sentido de f '{2 {operator} 4}' onde agora você pode atribuir o operador para fazer 2 + 4 ou 2 * 4 ou 2-4 ou etc
Skyler
Isso é praticamente equivalente a apenas fazer str(eval(...)), então certamente não é mais seguro do que eval.
kaya3
Parece ser o mesmo com exec / eval ...
Victor VosMottor agradece Monica
0

Use evalem um namespace limpo:

>>> ns = {'__builtins__': None}
>>> eval('2 ** 4', ns)
16

O namespace limpo deve impedir a injeção. Por exemplo:

>>> eval('__builtins__.__import__("os").system("echo got through")', ns)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute '__import__'

Caso contrário, você obteria:

>>> eval('__builtins__.__import__("os").system("echo got through")')
got through
0

Você pode querer dar acesso ao módulo de matemática:

>>> import math
>>> ns = vars(math).copy()
>>> ns['__builtins__'] = None
>>> eval('cos(pi/3)', ns)
0.50000000000000011
krawyoti
fonte
35
eval ("(1) .__ class __.__ bases __ [0] .__ subclasses __ () [81] ('echo got through'.split ())", {' builtins ': None}) #escapes your sandbox
Perkins
6
Python 3.4: eval("""[i for i in (1).__class__.__bases__[0].__subclasses__() if i.__name__.endswith('BuiltinImporter')][0]().load_module('sys').modules['sys'].modules['os'].system('/bin/sh')""", {'__builtins__': None})executa o shell bourne ...
Antti Haapala
8
Isso não é seguro . O código malicioso ainda pode ser executado.
Paradox de Fermi,
This is not safe- bem, acho que é tão seguro quanto usar o bash em geral. BTW: eval('math.sqrt(2.0)')<- "matemática". é necessário conforme escrito acima.
Hannu
0

Aqui está minha solução para o problema sem usar eval. Funciona com Python2 e Python3. Não funciona com números negativos.

$ python -m pytest test.py

test.py

from solution import Solutions

class SolutionsTestCase(unittest.TestCase):
    def setUp(self):
        self.solutions = Solutions()

    def test_evaluate(self):
        expressions = [
            '2+3=5',
            '6+4/2*2=10',
            '3+2.45/8=3.30625',
            '3**3*3/3+3=30',
            '2^4=6'
        ]
        results = [x.split('=')[1] for x in expressions]
        for e in range(len(expressions)):
            if '.' in results[e]:
                results[e] = float(results[e])
            else:
                results[e] = int(results[e])
            self.assertEqual(
                results[e],
                self.solutions.evaluate(expressions[e])
            )

solution.py

class Solutions(object):
    def evaluate(self, exp):
        def format(res):
            if '.' in res:
                try:
                    res = float(res)
                except ValueError:
                    pass
            else:
                try:
                    res = int(res)
                except ValueError:
                    pass
            return res
        def splitter(item, op):
            mul = item.split(op)
            if len(mul) == 2:
                for x in ['^', '*', '/', '+', '-']:
                    if x in mul[0]:
                        mul = [mul[0].split(x)[1], mul[1]]
                    if x in mul[1]:
                        mul = [mul[0], mul[1].split(x)[0]]
            elif len(mul) > 2:
                pass
            else:
                pass
            for x in range(len(mul)):
                mul[x] = format(mul[x])
            return mul
        exp = exp.replace(' ', '')
        if '=' in exp:
            res = exp.split('=')[1]
            res = format(res)
            exp = exp.replace('=%s' % res, '')
        while '^' in exp:
            if '^' in exp:
                itm = splitter(exp, '^')
                res = itm[0] ^ itm[1]
                exp = exp.replace('%s^%s' % (str(itm[0]), str(itm[1])), str(res))
        while '**' in exp:
            if '**' in exp:
                itm = splitter(exp, '**')
                res = itm[0] ** itm[1]
                exp = exp.replace('%s**%s' % (str(itm[0]), str(itm[1])), str(res))
        while '/' in exp:
            if '/' in exp:
                itm = splitter(exp, '/')
                res = itm[0] / itm[1]
                exp = exp.replace('%s/%s' % (str(itm[0]), str(itm[1])), str(res))
        while '*' in exp:
            if '*' in exp:
                itm = splitter(exp, '*')
                res = itm[0] * itm[1]
                exp = exp.replace('%s*%s' % (str(itm[0]), str(itm[1])), str(res))
        while '+' in exp:
            if '+' in exp:
                itm = splitter(exp, '+')
                res = itm[0] + itm[1]
                exp = exp.replace('%s+%s' % (str(itm[0]), str(itm[1])), str(res))
        while '-' in exp:
            if '-' in exp:
                itm = splitter(exp, '-')
                res = itm[0] - itm[1]
                exp = exp.replace('%s-%s' % (str(itm[0]), str(itm[1])), str(res))

        return format(exp)
GALERIA DE ARTE
fonte