Valutazione di un'espressione matematica in una stringa


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

Ciò restituisce il seguente errore:

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

So che evalpuò aggirare questo problema, ma non esiste un metodo migliore e, cosa più importante, più sicuro per valutare un'espressione matematica che viene memorizzata in una stringa?


6
^ è l'operatore XOR. Il valore atteso è 6. Probabilmente vuoi pow (2,4).
kgiannakakis

25
o più pitonicamente 2 ** 4
fortran

1
Se non vuoi usare eval, l'unica soluzione è implementare il parser grammaticale appropriato. Dai un'occhiata al pyparsing .
kgiannakakis

Risposte:


108

Il pyparsing può essere utilizzato per analizzare espressioni matematiche. In particolare, fourFn.py mostra come analizzare le espressioni aritmetiche di base. Di seguito, ho riavvolto fourFn in una classe parser numerica per un più facile riutilizzo.

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

Puoi usarlo in questo modo

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

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

180

eval è il male

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

Nota: anche se usi set __builtins__to Nonepotrebbe essere comunque possibile uscire usando l'introspezione:

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

Valuta l'espressione aritmetica 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)

È possibile limitare facilmente l'intervallo consentito per ciascuna operazione o qualsiasi risultato intermedio, ad esempio per limitare gli argomenti di input per 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 per limitare l'entità dei risultati intermedi:

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

Esempio

>>> 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:

29
Post molto interessante, grazie. Ho preso quel concetto e ho provato a creare una libreria che dovrebbe essere facile da usare: github.com/danthedeckie/simpleeval
Daniel Fairhead

può essere esteso per funzioni di import math?
Hotschke

2
Nota che ast.parsenon è sicuro. Ad esempio, ast.parse('()' * 1000000, '<string>', 'single')l'interprete si blocca.
Antti Haapala

1
@ AnttiHaapala buon esempio. È un bug nell'interprete Python? Ad ogni modo, un input di grandi dimensioni viene gestito banalmente, ad esempio utilizzando if len(expr) > 10000: raise ValueError.
jfs

1
@AnttiHaapala potresti fornire un esempio che non può essere risolto utilizzando l' len(expr)assegno? O il tuo punto è che ci sono bug nell'implementazione di Python e quindi è impossibile scrivere codice sicuro in generale?
jfs


10

Ok, quindi il problema con eval è che può sfuggire alla sua sandbox troppo facilmente, anche se te ne sbarazzi __builtins__. Tutti i metodi per uscire dalla sandbox si riducono a utilizzare getattro object.__getattribute__(tramite l' .operatore) per ottenere un riferimento a un oggetto pericoloso tramite un oggetto consentito ( ''.__class__.__bases__[0].__subclasses__o simile). getattrviene eliminato impostando __builtins__su None. object.__getattribute__è quello difficile, poiché non può essere semplicemente rimosso, sia perché objectè immutabile sia perché rimuoverlo spezzerebbe tutto. Tuttavia, __getattribute__è accessibile solo tramite l' .operatore, quindi eliminarlo dal tuo input è sufficiente per garantire che eval non possa sfuggire alla sua sandbox.
Nell'elaborazione delle formule, l'unico uso valido di un decimale è quando è preceduto o seguito da[0-9], quindi rimuoviamo semplicemente tutte le altre istanze di ..

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

Nota che mentre Python normalmente tratta 1 + 1.come 1 + 1.0, questo rimuoverà il trailing .e ti lascerà con 1 + 1. Potresti aggiungere ), e EOFall'elenco delle cose che è consentito seguire ., ma perché preoccuparsi?


Una domanda correlata con una discussione interessante può essere trovata qui .
djvg

3
Indipendentemente dal fatto che l'argomento sulla rimozione .sia corretto o meno al momento, questo lascia il potenziale per vulnerabilità di sicurezza se le future versioni di Python introducono una nuova sintassi che consente l'accesso a oggetti o funzioni non sicuri in qualche altro modo. Questa soluzione è già pericolosa in Python 3.6 a causa delle stringhe f, che consentono il seguente attacco:f"{eval('()' + chr(46) + '__class__')}" . Una soluzione basata sulla whitelist piuttosto che sulla blacklist sarà più sicura, ma in realtà è meglio risolvere questo problema senza del evaltutto.
kaya3

Questo è un ottimo punto sulle future funzionalità del linguaggio che introducono nuovi problemi di sicurezza.
Perkins

8

È possibile utilizzare il modulo ast e scrivere un NodeVisitor che verifica che il tipo di ogni nodo faccia parte di una whitelist.

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)

Poiché funziona tramite una whitelist anziché una blacklist, è sicuro. Le uniche funzioni e variabili a cui può accedere sono quelle a cui gli dai esplicitamente accesso. Ho popolato un dict con funzioni relative alla matematica in modo da poter fornire facilmente l'accesso a quelle se lo desideri, ma devi usarlo esplicitamente.

Se la stringa tenta di chiamare funzioni che non sono state fornite o di invocare metodi, verrà sollevata un'eccezione e non verrà eseguita.

Poiché utilizza il parser e l'analizzatore integrato di Python, eredita anche le regole di precedenza e promozione di Python.

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

Il codice sopra è stato testato solo su Python 3.

Se lo desideri, puoi aggiungere un decoratore timeout su questa funzione.


7

Il motivo evale execsono così pericolosi è che la compilefunzione predefinita genererà bytecode per qualsiasi espressione python valida e il valore predefinito evaloexec eseguirà qualsiasi bytecode python valido. Tutte le risposte fino ad oggi si sono concentrate sulla limitazione del bytecode che può essere generato (disinfettando l'input) o sulla costruzione del proprio linguaggio specifico del dominio utilizzando l'AST.

Invece, puoi facilmente creare una semplice evalfunzione che è incapace di fare qualcosa di nefasto e può facilmente avere controlli di runtime sulla memoria o sul tempo utilizzato. Ovviamente, se è semplice matematica, c'è una scorciatoia.

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]

Il modo in cui funziona è semplice, qualsiasi espressione matematica costante viene valutata in modo sicuro durante la compilazione e memorizzata come costante. L'oggetto codice restituito da compile è costituito da d, che è il bytecode LOAD_CONST, seguito dal numero della costante da caricare (di solito l'ultima nell'elenco), seguito daS , che è il bytecode per RETURN_VALUE. Se questa scorciatoia non funziona, significa che l'input dell'utente non è un'espressione costante (contiene una variabile o una chiamata di funzione o simili).

Questo apre anche la porta ad alcuni formati di input più sofisticati. Per esempio:

stringExp = "1 + cos(2)"

Ciò richiede effettivamente la valutazione del bytecode, che è ancora abbastanza semplice. Il bytecode Python è un linguaggio orientato allo stack, quindi tutto è una questione semplice TOS=stack.pop(); op(TOS); stack.put(TOS)o simile. La chiave è implementare solo i codici operativi sicuri (caricamento / memorizzazione di valori, operazioni matematiche, restituzione di valori) e non non sicuri (ricerca degli attributi). Se vuoi che l'utente sia in grado di chiamare le funzioni (l'intera ragione per non usare la scorciatoia sopra), semplifica l'implementazione di CALL_FUNCTIONconsentire solo le funzioni in un elenco "sicuro".

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

Ovviamente, la versione reale di questo sarebbe un po 'più lunga (ci sono 119 opcode, 24 dei quali sono legati alla matematica). L'aggiunta STORE_FASTe un paio di altri consentirebbero input simili 'x=5;return x+xo simili, banalmente facilmente. Può anche essere usato per eseguire funzioni create dall'utente, a patto che le funzioni create dall'utente siano esse stesse eseguite tramite VMeval (non renderle richiamabili !!! o potrebbero essere usate come callback da qualche parte). La gestione dei loop richiede il supporto per i gotobytecode, il che significa cambiare da un essere il più ovvio).for iteratore a whilemantenere un puntatore all'istruzione corrente, ma non è troppo difficile. Per resistere al DOS, il ciclo principale dovrebbe controllare quanto tempo è passato dall'inizio del calcolo e alcuni operatori dovrebbero negare l'input oltre un limite ragionevole (BINARY_POWER

Sebbene questo approccio sia un po 'più lungo di un semplice analizzatore grammaticale per espressioni semplici (vedi sopra per compileprendere semplicemente la costante compilata), si estende facilmente a input più complicati e non richiede di occuparsi della grammatica ( prendi qualsiasi cosa arbitrariamente complicata e la riduce a una sequenza di semplici istruzioni).


6

Penso che lo userei eval(), ma prima verificherei per assicurarmi che la stringa sia un'espressione matematica valida, al contrario di qualcosa di dannoso. Potresti usare una regex per la convalida.

eval() accetta anche argomenti aggiuntivi che puoi usare per limitare lo spazio dei nomi in cui opera per una maggiore sicurezza.


3
Ma, ovviamente, non fare affidamento sulle espressioni regolari per convalidare espressioni matematiche arbitrarie.
High Performance Mark

@ Marchio ad alte prestazioni: Sì, credo che dipenda dal tipo di espressioni matematiche che ha in mente. . . per esempio, basta la semplice aritmetica con i numeri e +, -, *, /, **, (, )o qualcosa di più complicato
Tim Goodman

@Tim - è il () che mi preoccupa, o meglio il (((((())))))). In verità, penso che OP dovrebbe preoccuparsi per loro, la mia fronte è aperta dai problemi di OP.
High Performance Mark

2
Non usare eval()se non controlli l'input anche se limiti lo spazio dei nomi, ad esempio eval("9**9**9**9**9**9**9**9", {'__builtins__': None})consuma CPU, memoria.
jfs

3
Limitare lo spazio dei nomi di eval non aumenta la sicurezza .
Antti Haapala

5

Questa è una risposta enormemente tardiva, ma ritengo utile per riferimento futuro. Piuttosto che scrivere il tuo parser matematico (sebbene l'esempio di pyparsing sopra sia ottimo) potresti usare SymPy. Non ho molta esperienza con esso, ma contiene un motore matematico molto più potente di quello che chiunque potrebbe scrivere per un'applicazione specifica e la valutazione delle espressioni di base è molto semplice:

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

Davvero molto bello! A from sympy import *offre molto più supporto alle funzioni, come funzioni trigonometriche, funzioni speciali, ecc., Ma l'ho evitato qui per mostrare cosa proviene da dove.


3
Sympy è "sicuro"? Sembra che ci siano numerosi post che suggeriscono che sia un wrapper attorno a eval () che potrebbe essere sfruttato allo stesso modo. Inoltre evalfnon accetta numpy ndarrays.
Mark Mikofski

14
No sympy non è sicuro per input non attendibili. Prova sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")queste chiamate subprocess.Popen()che ho passato lsinvece di rm -rf /. L'indice sarà probabilmente diverso su altri computer. Questa è una variante dell'exploit di Ned Batchelder
Mark Mikofski

1
In effetti, non si aggiunge affatto alla sicurezza.
Antti Haapala

4

[So che questa è una vecchia domanda, ma vale la pena sottolineare nuove soluzioni utili non appena vengono visualizzate]

A partire da python3.6, questa capacità è ora incorporata nel linguaggio , coniato "f-strings" .

Vedi: PEP 498 - Interpolazione letterale di stringhe

Ad esempio (nota il fprefisso):

f'{2**4}'
=> '16'

7
Link molto interessante. Ma immagino che le stringhe f siano qui per rendere più semplice la scrittura del codice sorgente, mentre la domanda sembra riguardare il lavoro con le stringhe all'interno di variabili (possibilmente da fonti non attendibili). Le stringhe f non possono essere utilizzate in questo caso.
Bernhard

c'è un modo per fare qualcosa all'effetto di f '{2 {operator} 4}' dove ora puoi assegnare all'operatore di fare 2 + 4 o 2 * 4 o 2-4 o ecc.
Skyler

Questo è praticamente equivalente al solo fare str(eval(...)), quindi non è certamente più sicuro di eval.
kaya3

Sembra essere lo stesso con exec / eval ...
Victor VosMottor ringrazia Monica

0

Usa evalin uno spazio dei nomi pulito:

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

Lo spazio dei nomi pulito dovrebbe impedire l'iniezione. Per esempio:

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

Altrimenti otterrai:

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

Potresti voler dare accesso al modulo di matematica:

>>> import math
>>> ns = vars(math).copy()
>>> ns['__builtins__'] = None
>>> eval('cos(pi/3)', ns)
0.50000000000000011

35
eval ("(1) .__ class __.__ basi __ [0] .__ sottoclassi __ () [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})esegue la bourne shell ...
Antti Haapala

8
Questo non è sicuro . Il codice dannoso può ancora essere eseguito.
Paradosso di Fermi

This is not safe- beh, credo che sia sicuro quanto usare bash in generale. BTW: eval('math.sqrt(2.0)')<- "matematica". è richiesto come scritto sopra.
Hannu

0

Ecco la mia soluzione al problema senza usare eval. Funziona con Python2 e Python3. Non funziona con numeri negativi.

$ 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.