Оцінювання математичного виразу в рядку


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

Це повертає таку помилку:

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

Я знаю, що це evalможе обійтись, але чи не існує кращого і, що ще важливіше, більш безпечного методу для оцінки математичного виразу, який зберігається в рядку?


6
^ - оператор XOR. Очікуване значення 6. Ви, мабуть, хочете порошок (2,4).
kgiannakakis

25
або більше пітонічно 2 ** 4
фортран

1
Якщо ви не хочете використовувати eval, єдиним рішенням є реалізація відповідного граматичного аналізатора. Погляньте на піпарінг .
kgiannakakis

Відповіді:


108

Піпаринг можна використовувати для розбору математичних виразів. Зокрема, fourFn.py показує, як розбирати основні арифметичні вирази. Нижче я переписав fourFn в числовий клас аналізатора для легшого використання.

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

Ви можете використовувати його так

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

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

180

eval є зло

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

Примітка. Навіть якщо ви використовуєте встановлений __builtins__для Noneнього спосіб, все одно можливо вийти з допомогою самоаналізу:

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

Оцініть арифметичний вираз за допомогою 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)

Ви можете легко обмежити дозволений діапазон для кожної операції або будь-якого проміжного результату, наприклад, обмежити вхідні аргументи для 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

Або обмежити величину проміжних результатів:

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

Приклад

>>> 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
Дуже класний пост, спасибі Я взяв цю концепцію і спробував створити бібліотеку, яка повинна бути простою у користуванні: github.com/danthedeckie/simpleeval
Daniel Fairhead

чи можна це продовжити для функцій import math?
Hotschke

2
Зверніть увагу, що ast.parseце не безпечно. Наприклад, ast.parse('()' * 1000000, '<string>', 'single')виходить з ладу перекладач.
Антті Хаапала

1
@AnttiHaapala хороший приклад. Це помилка в інтерпретаторі Python? У будь-якому випадку, великий вхід тривіально обробляється, наприклад, за допомогою if len(expr) > 10000: raise ValueError.
jfs

1
@AnttiHaapala чи можете ви навести приклад, який неможливо виправити за допомогою len(expr)чека? Або ваша думка полягає в тому, що в реалізації Python є помилки, і тому взагалі неможливо написати безпечний код?
jfs

13

Деякі безпечніші альтернативи eval()та * :sympy.sympify().evalf()

* SymPy sympifyтакож небезпечний відповідно до наступного попередження з документації.

Попередження: Зауважте, що ця функція використовує eval, і тому її не слід використовувати на несанізованому вході.


10

Гаразд, тому проблема з eval полягає в тому, що він може надто легко уникнути своєї пісочниці, навіть якщо позбутися __builtins__. Усі методи виходу з пісочниці зводиться до використання getattrабо object.__getattribute__(через .оператора) для отримання посилання на якийсь небезпечний об'єкт через якийсь дозволений об'єкт ( ''.__class__.__bases__[0].__subclasses__або подібний). getattrусувається встановленням __builtins__на None. object.__getattribute__є складним, оскільки його неможливо просто видалити, як через objectнепорушність, так і через те, що видалення все порушить. Однак, __getattribute__це доступне лише через .оператора, тому очищення, яке з вашого вводу є достатнім для того, щоб eval не міг вийти з його пісочниці.
При обробці формул єдине дійсне використання десяткової особи є тоді, коли їй передують або слідують за нею[0-9], тому ми просто видаляємо всі інші екземпляри ..

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

Зауважте, що, хоча python зазвичай трактує 1 + 1.як 1 + 1.0, це видалить трейлінг .і залишить вас 1 + 1. Ви можете додати ), і EOFдо списку речей, дозволених слідувати ., але навіщо це турбувати?


Пов'язане питання з цікавою дискусією можна знайти тут .
djvg

3
Незалежно від того, чи є аргумент про видалення .правильним на даний момент, це не дає можливості вразливості безпеки, якщо майбутні версії Python запровадять новий синтаксис, що дозволяє отримати доступ до небезпечних об'єктів чи функцій іншим способом. Це рішення вже небезпечно в Python 3.6 F-рядки, які дозволяють зробити наступні атаки: f"{eval('()' + chr(46) + '__class__')}". Рішення на основі білого списку, а не чорного списку буде безпечнішим, але насправді краще вирішити цю проблему evalвзагалі.
kaya3

Це прекрасний момент щодо майбутніх мовних особливостей, що представляють нові проблеми безпеки.
Перкінс

8

Ви можете використовувати модуль ast і написати NodeVisitor, який підтверджує, що тип кожного вузла є частиною білого списку.

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)

Оскільки він працює через білий список, а не з чорного списку, це безпечно. Єдині функції та змінні, до яких він може отримати доступ, - це ті, до яких явно ви надаєте їм доступ. Я заповнив дикт із функціями, пов'язаними з математикою, щоб ви могли легко надати доступ до тих, якщо хочете, але ви мусите це прямо використовувати.

Якщо рядок намагається викликати функції, які не були надані, або викликати будь-які методи, буде винято виняток, і він не буде виконуватися.

Оскільки для цього використовується вбудований в аналізатор та оцінювач Python, він також успадковує пріоритет і правила просування Python.

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

Вищевказаний код був протестований лише на Python 3.

За бажанням можна додати в цю функцію декоратор тайм-ауту.


7

Причина evalі execнастільки небезпечна, що compileфункція за замовчуванням генерує байт-код для будь-якого дійсного вираження python, а за замовчуванням evalабо execвиконує будь-який дійсний байт-код python. Всі відповіді на сьогодні були зосереджені на обмеженні байтового коду, який можна генерувати (за допомогою санітарного введення) або на створенні власної мови, що залежить від домену за допомогою AST.

Натомість ви можете легко створити просту evalфункцію, яка не здатна робити нічого шкідливого і може легко перевірити час роботи пам'яті або використаний час. Звичайно, якщо це проста математика, тоді є ярлик.

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]

Те, як це працює, є простим, будь-який постійний математичний вираз надійно оцінюється під час компіляції та зберігається як константа. Об'єкт коду, повернутий компіляцією, складається з d, який є байт- кодом для LOAD_CONST, за яким слід номер константи для завантаження (як правило, останній у списку), за яким слідує S, яким є байт-код для RETURN_VALUE. Якщо цей ярлик не працює, це означає, що введення користувача не є постійним виразом (містить виклик змінної або функції або подібне).

Це також відкриває двері для деяких більш складних форматів введення. Наприклад:

stringExp = "1 + cos(2)"

Для цього потрібно фактично оцінити байт-код, який все ще досить простий. Байт-код Python - це орієнтована на стек мова, тому все є простим питанням TOS=stack.pop(); op(TOS); stack.put(TOS)або подібним. Ключ полягає в тому, щоб реалізувати лише безпечні коди (завантаження / зберігання значень, математичні операції, повернення значень), а не небезпечні (пошук атрибутів). Якщо ви хочете, щоб користувач міг викликати функції (вся причина, щоб не використовувати ярлик вище), просто зробіть реалізацію CALL_FUNCTIONлише дозволених функцій у списку "безпечних".

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

Очевидно, справжня версія цього була б трохи довшою (є 119 опкодів, 24 з яких пов'язані з математикою). Додавання STORE_FASTта ще декілька інших людей дозволять вводити подібні 'x=5;return x+xчи подібні дані, тривіально легко. Він навіть може бути використаний для виконання створених користувачем функцій, доки створені користувачем функції самі виконуються через VMeval (не робіть їх для дзвінка !!! Для обробки циклів потрібна підтримка gotoбайткодів, що означає перехід від forітератора до найбільш очевидного).while та підтримку покажчика на поточну інструкцію, але це не надто складно. Для опору DOS основний цикл повинен перевірити, скільки часу минуло з моменту початку обчислення, а певні оператори повинні заперечувати введення даних за деякий розумний межа (BINARY_POWER

Хоча цей підхід дещо довший, ніж простий граматичний аналізатор простих виразів (див. Вище про просто захоплення компільованої константи), він легко поширюється на складніші дані і не потребує роботи з граматикою ( compileприймати що завгодно складно і зводить до послідовність простих інструкцій).


6

Думаю, я би скористався eval(), але спершу перевірив би, щоб переконатися, що рядок є дійсним математичним виразом, на відміну від чогось шкідливого. Ви можете використовувати регулярний вираз для перевірки.

eval() також бере додаткові аргументи, які ви можете використовувати для обмеження простору імен, в яких він працює, для більшої безпеки.


3
Але, звичайно, не покладайтеся на регулярні вирази для перевірки довільних математичних виразів.
Марка високої продуктивності

@ Високопродуктивна марка: Так, я думаю, це залежить від того, які математичні вирази він має на увазі. . . наприклад, проста арифметика з числами і +, -, *, /, **, (, )або що - то більш складним
Тім Гудман

@Tim - це () я переживаю, а точніше (((((())))))). По правді кажучи, я думаю, що ОП має хвилюватися за них, моє чело розгублене проблемами ОП.
Марка високої продуктивності

2
Не використовуйте, eval()якщо ви не контролюєте введення даних, навіть якщо ви обмежуєте область імен, наприклад, eval("9**9**9**9**9**9**9**9", {'__builtins__': None})споживає процесор, пам'ять.
jfs

3
Обмеження простору імен eval не додає безпеки .
Антті Хаапала

5

Це масово пізня відповідь, але я вважаю корисним для подальшого ознайомлення. Замість того, щоб написати свій власний математичний аналізатор (хоча приклад шикарного розгляду вище), ви можете використовувати SymPy. Я не маю великого досвіду роботи з цим, але він містить набагато більш потужний математичний движок, ніж хтось, ймовірно, пише для конкретної програми, і базове оцінювання виразів дуже просто:

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

Дуже круто насправді! A from sympy import *приносить набагато більшу підтримку функцій, таких як триггерні функції, спеціальні функції тощо, але я уникав цього, щоб показати, що звідки походить.


3
Чи симпатія "безпечна"? Здається, є численні публікації, які дозволяють припустити, що це обгортка навколо eval (), яку можна експлуатувати так само. Також evalfне приймає нудні ndarrays.
Марк Мікофський

14
Ніяка симпатія не є безпечною для ненадійного введення. Спробуйте sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")це дзвінки, subprocess.Popen()які я передав lsзамість rm -rf /. На інших комп'ютерах індекс, ймовірно, буде іншим. Це варіант подвигу Неда
Батчелдера

1
Дійсно, це зовсім не підвищує безпеку.
Антті Хаапала

4

[Я знаю, що це старе питання, але варто вказати на нові корисні рішення, коли вони з'являться]

Починаючи з python3.6, ця можливість тепер вбудована в мову , сформульована "f-рядками" .

Див.: PEP 498 - Інтерполяція літеральної струни

Наприклад (зверніть увагу на fпрефікс):

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

7
Дуже цікаве посилання. Але я думаю, що тут є f-рядки для полегшення написання вихідного коду, тоді як, мабуть, питання стосується роботи з рядками всередині змінних (можливо, з ненадійних джерел). f-string не можна використовувати в цьому випадку.
Бернхард

чи є можливість зробити щось для ефекту f '{2 {operator} 4}', де тепер можна призначити оператору робити 2 + 4 або 2 * 4 або 2-4 або тощо
Skyler

Це практично еквівалентно просто робити str(eval(...)), тому це, звичайно, не безпечніше eval.
kaya3

Здається, те ж саме з exec / eval ...
Віктор VosMottor дякує Моніці

0

Використання evalв чистому просторі імен:

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

Чистий простір імен повинен запобігати ін'єкції. Наприклад:

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

Інакше ви отримаєте:

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

Ви можете надати доступ до математичного модуля:

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

35
eval ("(1) .__ клас __.__ бази __ [0] .__ підкласи __ () [81] ('ехо пройшло через'. split ())", {' вбудовані ': жоден}) #escapes ваша пісочниця
Перкінс,

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})виконує обстріл бурна ...
Антті Хаапала

8
Це не безпечно . Шкідливий код все ще може бути виконаний.
Парадокс Фермі

This is not safe- Ну, я вважаю, це так само безпечно, як і використання bash в цілому. BTW: eval('math.sqrt(2.0)')<- "математика". потрібно, як написано вище.
Ханну

0

Ось моє рішення проблеми без використання eval. Працює з Python2 та Python3. Не працює з від’ємними числами.

$ 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])
            )

рішення.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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.