Evaluar una expresión matemática en una cadena

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

Esto devuelve el siguiente error:

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

Sé que se evalpuede solucionar esto, pero ¿no existe un método mejor y, lo que es más importante, más seguro para evaluar una expresión matemática que se almacena en una cadena?

Pieter
fuente
6
^ es el operador XOR. El valor esperado es 6. Probablemente desee pow (2,4).
kgiannakakis
25
o más pitónicamente 2 ** 4
fortran
1
Si no desea utilizar eval, la única solución es implementar el analizador gramatical apropiado. Eche un vistazo a pyparsing .
kgiannakakis

Respuestas:

108

Pyparsing se puede utilizar para analizar expresiones matemáticas. En particular, fourFn.py muestra cómo analizar expresiones aritméticas básicas. A continuación, he vuelto a envolver fourFn en una clase de analizador numérico para facilitar su reutilización.

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

Puedes usarlo así

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

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

eval es malvado

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

Nota: incluso si usa set __builtins__to None, es posible que se rompa usando la introspección:

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

Evaluar la expresión 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)

Puede limitar fácilmente el rango permitido para cada operación o cualquier resultado intermedio, por ejemplo, para limitar los 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

O para limitar la magnitud de los resultados intermedios:

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_)

Ejemplo

>>> 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
fuente
29
Muy buena publicación, Gracias. Tomé ese concepto e intenté crear una biblioteca que debería ser fácil de usar: github.com/danthedeckie/simpleeval
Daniel Fairhead
¿Se puede extender esto para funciones de import math?
Hotschke
2
Tenga en cuenta que ast.parseno es seguro. Por ejemplo, ast.parse('()' * 1000000, '<string>', 'single')el intérprete se bloquea.
Antti Haapala
1
@AnttiHaapala buen ejemplo. ¿Es un error en el intérprete de Python? De todos modos, la entrada grande se maneja trivialmente, por ejemplo, usando if len(expr) > 10000: raise ValueError.
jfs
1
@AnttiHaapala, ¿podría proporcionar un ejemplo que no se pueda arreglar con el len(expr)cheque? ¿O su punto es que hay errores en la implementación de Python y, por lo tanto, es imposible escribir código seguro en general?
jfs
13

Algunas alternativas más seguras para eval()e * :sympy.sympify().evalf()

* SymPy sympifytampoco es seguro según la siguiente advertencia de la documentación.

Advertencia: Tenga en cuenta que esta función se utiliza evaly, por lo tanto, no debe utilizarse en entradas no higienizadas.

Mark Mikofski
fuente
10

Bien, entonces el problema con eval es que puede escapar de su caja de arena con demasiada facilidad, incluso si se deshace de __builtins__. Todos los métodos para escapar de la caja de arena se reducen a usar getattro object.__getattribute__(a través del .operador) para obtener una referencia a algún objeto peligroso a través de algún objeto permitido ( ''.__class__.__bases__[0].__subclasses__o similar). getattrse elimina estableciendo __builtins__en None. object.__getattribute__es la difícil, ya que no se puede quitar simplemente, tanto porque objectes inmutable como porque quitarlo rompería todo. Sin embargo, __getattribute__solo se puede acceder a través del .operador, por lo que purgar eso desde su entrada es suficiente para garantizar que eval no pueda escapar de su caja de arena.
En el procesamiento de fórmulas, el único uso válido de un decimal es cuando está precedido o seguido de[0-9], por lo que simplemente eliminamos todas las demás instancias de ..

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

Tenga en cuenta que, si bien Python normalmente lo trata 1 + 1.como 1 + 1.0, esto eliminará el final .y lo dejará con 1 + 1. Podría agregar ), y EOFa la lista de cosas permitidas a continuación ., pero ¿por qué molestarse?

Perkins
fuente
Aquí se puede encontrar una pregunta relacionada con una discusión interesante .
djvg
3
Ya sea que el argumento sobre la eliminación .sea ​​correcto o no en este momento, esto deja la posibilidad de vulnerabilidades de seguridad si las versiones futuras de Python introducen una nueva sintaxis que permita acceder a objetos o funciones inseguros de alguna otra manera. Esta solución ya no es seguro en Python 3.6 debido a f-secuencias, que permiten el ataque siguiente: f"{eval('()' + chr(46) + '__class__')}". Una solución basada en listas blancas en lugar de listas negras será más segura, pero realmente es mejor resolver este problema sin evalnada.
kaya3
Ese es un punto excelente sobre las funciones del lenguaje futuras que presentan nuevos problemas de seguridad.
Perkins
8

Puede usar el módulo ast y escribir un NodeVisitor que verifique que el tipo de cada nodo es parte de una lista blanca.

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)

Debido a que funciona a través de una lista blanca en lugar de una lista negra, es seguro. Las únicas funciones y variables a las que puede acceder son aquellas a las que le da acceso explícitamente. Completé un diccionario con funciones relacionadas con las matemáticas para que pueda proporcionar acceso fácilmente a ellas si lo desea, pero debe usarlo explícitamente.

Si la cadena intenta llamar a funciones que no se han proporcionado, o invoca algún método, se generará una excepción y no se ejecutará.

Debido a que utiliza el analizador y evaluador integrado de Python, también hereda las reglas de precedencia y promoción de Python.

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

El código anterior solo se ha probado en Python 3.

Si lo desea, puede agregar un decorador de tiempo de espera en esta función.

Kevin
fuente
7

La razón evaly execson tan peligrosos es que la compilefunción predeterminada generará un código de bytes para cualquier expresión válida de Python, y la predeterminada evalo execejecutará cualquier código de bytes de Python válido. Todas las respuestas hasta la fecha se han centrado en restringir el código de bytes que se puede generar (desinfectando la entrada) o en la construcción de su propio lenguaje específico de dominio utilizando el AST.

En su lugar, puede crear fácilmente una evalfunción simple que es incapaz de hacer nada nefasto y puede tener fácilmente verificaciones de tiempo de ejecución en la memoria o el tiempo utilizado. Por supuesto, si se trata de matemáticas simples, entonces hay un atajo.

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]

La forma en que esto funciona es simple, cualquier expresión matemática constante se evalúa de manera segura durante la compilación y se almacena como una constante. El objeto de código devuelto por la compilación consta de d, cuál es el código de bytes para LOAD_CONST, seguido del número de la constante a cargar (generalmente el último en la lista), seguido de S, que es el código de bytes para RETURN_VALUE. Si este atajo no funciona, significa que la entrada del usuario no es una expresión constante (contiene una variable o llamada de función o similar).

Esto también abre la puerta a algunos formatos de entrada más sofisticados. Por ejemplo:

stringExp = "1 + cos(2)"

Esto requiere evaluar realmente el código de bytes, que aún es bastante simple. El código de bytes de Python es un lenguaje orientado a pilas, por lo que todo es una cuestión simple TOS=stack.pop(); op(TOS); stack.put(TOS)o similar. La clave es implementar solo los códigos de operación que son seguros (carga / almacenamiento de valores, operaciones matemáticas, valores de retorno) y no inseguros (búsqueda de atributos). Si desea que el usuario pueda llamar a funciones (la CALL_FUNCTIONúnica razón para no usar el atajo anterior), simplemente haga su implementación de solo permitir funciones en una 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, la versión real de esto sería un poco más larga (hay 119 códigos de operación, 24 de los cuales están relacionados con las matemáticas). Agregar STORE_FASTy un par más permitiría una entrada similar 'x=5;return x+xo similar, de manera trivialmente fácil. Incluso se puede usar para ejecutar funciones creadas por el usuario, siempre que las funciones creadas por el usuario se ejecuten a través de VMeval (¡no las haga invocables! O podrían usarse como devolución de llamada en algún lugar). El manejo de bucles requiere soporte para los gotocódigos de bytes, lo que significa cambiar de un foriterador whiley mantener un puntero a la instrucción actual, pero no es demasiado difícil. Para resistir a DOS, el bucle principal debe verificar cuánto tiempo ha pasado desde el inicio del cálculo, y ciertos operadores deben negar la entrada por encima de algún límite razonable (BINARY_POWER siendo el más obvio).

Si bien este enfoque es algo más largo que un simple analizador gramatical para expresiones simples (vea más arriba acerca de simplemente tomar la constante compilada), se extiende fácilmente a entradas más complicadas y no requiere lidiar con la gramática ( compiletome cualquier cosa arbitrariamente complicada y reduzca a una secuencia de instrucciones sencillas).

Perkins
fuente
6

Creo que lo usaría eval(), pero primero verificaría que la cadena sea una expresión matemática válida, en lugar de algo malicioso. Puede usar una expresión regular para la validación.

eval() también toma argumentos adicionales que puede usar para restringir el espacio de nombres en el que opera para mayor seguridad.

Tim Goodman
fuente
3
Pero, por supuesto, no confíe en expresiones regulares para validar expresiones matemáticas arbitrarias.
Marca de alto rendimiento
@ Marca de alto rendimiento: Sí, supongo que depende del tipo de expresiones matemáticas que tenga en mente. . . por ejemplo, simplemente aritmética simple con los números y +, -, *, /, **, (, )o algo más complicado
Tim Goodman
@Tim - es el () lo que me preocupa, o más bien el ((((())))))). En verdad, creo que OP debería preocuparse por ellos, mi frente no está arrugada por los problemas de OP.
Marca de alto rendimiento
2
No lo use eval()si no controla la entrada, incluso si restringe el espacio de nombres, por ejemplo, eval("9**9**9**9**9**9**9**9", {'__builtins__': None})consume CPU, memoria.
jfs
3
Restringir el espacio de nombres de eval no aumenta la seguridad .
Antti Haapala
5

Esta es una respuesta masivamente tardía, pero creo que es útil para futuras referencias. En lugar de escribir su propio analizador matemático (aunque el ejemplo de pyparsing anterior es excelente), podría usar SymPy. No tengo mucha experiencia con él, pero contiene un motor matemático mucho más poderoso de lo que cualquiera pueda escribir para una aplicación específica y la evaluación de la expresión básica es muy 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

¡Muy guay! A from sympy import *trae mucho más soporte de funciones, como funciones trigonométricas, funciones especiales, etc., pero lo he evitado aquí para mostrar qué viene de dónde.

Andybuckley
fuente
3
¿Es sympy "seguro"? Parece haber numerosas publicaciones que sugieren que es una envoltura alrededor de eval () que podría explotarse de la misma manera. Tampoco evalftoma muchos Ndarrays.
Mark Mikofski
14
Ningún sympy no es seguro para entradas no confiables. Pruebe sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")estas llamadas subprocess.Popen()que pasé en lslugar de rm -rf /. El índice probablemente será diferente en otras computadoras. Esta es una variante del exploit de Ned Batchelder
Mark Mikofski
1
De hecho, no aumenta la seguridad en absoluto.
Antti Haapala
4

[Sé que esta es una pregunta antigua, pero vale la pena señalar nuevas soluciones útiles a medida que surgen]

Desde python3.6, esta capacidad ahora está integrada en el lenguaje , acuñado "f-strings" .

Ver: PEP 498 - Interpolación de cadenas literal

Por ejemplo (tenga en cuenta el fprefijo):

f'{2**4}'
=> '16'
shx2
fuente
7
Enlace muy interesante. Pero supongo que las f-strings están aquí para facilitar la escritura del código fuente, mientras que la pregunta parece ser sobre trabajar con cadenas dentro de variables (posiblemente de fuentes no confiables). En ese caso, no se pueden utilizar cadenas f.
Bernhard
¿Hay alguna manera de hacer algo como f '{2 {operator} 4}' donde ahora puede asignar al operador para que haga 2 + 4 o 2 * 4 o 2-4 o etc.?
Skyler
Esto es prácticamente equivalente a simplemente hacerlo str(eval(...)), por lo que ciertamente no es más seguro que eval.
kaya3
Parece ser lo mismo con exec / eval ...
Victor VosMottor agradece a Monica
0

Usar evalen un espacio de nombres limpio:

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

El espacio de nombres limpio debería evitar la inyección. Por ejemplo:

>>> 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__'

De lo contrario, obtendría:

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

Es posible que desee dar acceso al módulo de matemáticas:

>>> import math
>>> ns = vars(math).copy()
>>> ns['__builtins__'] = None
>>> eval('cos(pi/3)', ns)
0.50000000000000011
Krawyoti
fuente
35
eval ("(1) .__ class __.__ bases __ [0] .__ subclasses __ () [81] ('echo got through'.split ())", {' builtins ': None}) #escape 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})ejecuta el shell de Bourne ...
Antti Haapala
8
Esto no es seguro . Aún se puede ejecutar código malicioso.
Paradoja de Fermi
This is not safeBueno, creo que es tan seguro como usar bash en general. Por cierto: eval('math.sqrt(2.0)')<- "matemáticas". se requiere como se indica arriba.
Hannu
0

Aquí está mi solución al problema sin usar eval. Funciona con Python2 y Python3. No funciona con 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)
GALERÍA DE ARTE
fuente