programing

문자열에서 수학식 평가

batch 2023. 6. 25. 18:34
반응형

문자열에서 수학식 평가

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이 문제를 해결할 수 있지만, 더 중요한 것은, 문자열에 저장되어 있는 수학적 표현식을 평가할 수 있는 더 좋고 안전한 방법이 없을까요?

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:

파이퍼싱은 수학식을 구문 분석하는 데 사용될 수 있습니다.특히, fourFn.py 은 기본적인 산술 표현식을 구문 분석하는 방법을 보여줍니다.아래에, 저는 더 쉽게 재사용할 수 있도록 4Fn을 숫자 파서 클래스로 다시 포장했습니다.

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

▁에 대한 더 안전한 대안 몇 가지.eval()및 :

설명서의 다음 경고에 따르면 *SymPy도 안전하지 않습니다.

경고:이 기능은 다음을 사용합니다.eval따라서 비위생적인 입력에 사용해서는 안 됩니다.

ㅠㅠeval그리고.exec너무 위험하기 때문에 기본값은compile한 python에 대한 하며, 은 "" "" " " " " " 입니다.eval또는exec유효한 파이썬 바이트 코드를 실행합니다.지금까지 모든 답변은 입력을 검사하여 생성할 수 있는 바이트 코드를 제한하거나 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수가 그 를 이으며, 로할일상(수다으로목마상수다지막표니록의됩시음에드적반일▁followed)입니다.S은 의바트코다니입드의 입니다.RETURN_VALUE이 바로 가기가 작동하지 않으면 사용자 입력이 상수 식(변수 또는 함수 호출 포함)이 아님을 의미합니다.

이를 통해 보다 정교한 입력 형식을 사용할 수 있습니다.예:

stringExp = "1 + cos(2)"

이를 위해서는 실제로 바이트 코드를 평가해야 하는데, 이는 여전히 매우 간단합니다. 바이트코드는 언어이기 입니다.TOS=stack.pop(); op(TOS); stack.put(TOS)또는 그와 유사합니다.핵심은 안전하지 않은 opcode(로드/저장 값, 산술 연산, 반환 값)만 구현하고 안전하지 않은 opcode(속성 조회)는 구현하는 것입니다.사용자가 함수를 호출할 수 있기를 원한다면(위의 바로 가기를 사용하지 않는 전체 이유), 간단히 다음을 구현할 수 있습니다.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 또 몇 는 그고몇리몇다사같은은입것허다입니용할력을들람른▁like다▁and것▁allow▁for니▁input와 같은 입력을 허용할 것입니다.'x=5;return x+x또는 유사하지만, 아주 쉽게.사용자가 생성한 함수가 VMeval을 통해 자체적으로 실행되는 한 사용자 생성 함수를 실행하는 데도 사용할 수 있습니다(호출 가능으로 만들지 마십시오!).또는 어딘가에서 콜백으로 사용될 수 있습니다.루프를 처리하려면 다음에 대한 지원이 필요합니다.goto는 바트코드, 즉에변는것의다을니미에서 하는 것을 합니다.for에서 에한반자까지.while현재 명령에 대한 포인터를 유지하는 것은 어렵지 않습니다.DOS에 후 해야 하며, 는 어떤 인 제한", "") 이상의 입력을 거부해야 .BINARY_POWER가장 명백함).

표현을 문법 상수를 는 위 ), 더입력으로 .compile임의로 복잡한 모든 것을 가져다가 일련의 간단한 지시로 줄입니다.

좋아요, 그럼 평가의 문제는 샌드박스를 너무 쉽게 탈출할 수 있다는 것입니다. 설령 당신이 그것을 없애더라도 말입니다.__builtins__샌드박스를 탈출하는 모든 방법은 다음과 같습니다.getattr또는object.__getattribute__)로 ).operator)된 객체operator)를 통해 에 대한 를 얻기 합니다.''.__class__.__bases__[0].__subclasses__또는 유사). getattr▁setting를 설정하여 제거됩니다.__builtins__None.object.__getattribute__것은 할 수 입니다. 둘 다 그은단제수어것때없다입, 둘다니운려에문것 때문입니다.object그것을 제거하면 모든 것이 부서지기 때문에 불변입니다. 하만지,__getattribute__는 를통서액수있다니습을 만 액세스할 수 ..평가가 샌드박스를 벗어날 수 없도록 하기 위해 입력에서 삭제하는 것으로 충분합니다.
공식을 처리할 때, 소수의 유일한 유효한 사용은 다음과 같은 경우입니다.[0-9]그래서 우리는 단지 다른 모든 사례들을 제거합니다...

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

으로 python을 하지만 python은 python을 처리합니다.1 + 1.~하듯이1 + 1.0이렇게 하면 후행이 제거됩니다..그리고 당신에게 남겨주세요.1 + 1추가할 수 있습니다.),,그리고.EOF뒤에 따를 수 있는 것들의 목록에..그런데 왜 신경쓰죠?

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에서만 테스트되었습니다.

원하는 경우 이 기능에 시간 초과 장식기를 추가할 수 있습니다.

퍼킨스의 놀라운 접근 방식을 바탕으로 간단한 대수적 표현(함수나 변수 없음)에 대한 그의 "바로 가기"를 업데이트하고 개선했습니다.이제 Python 3.6+에서 작동하며 몇 가지 함정을 피할 수 있습니다.

import re

# Kept outside simple_eval() just for performance
_re_simple_eval = re.compile(rb'd([\x00-\xFF]+)S\x00')

def simple_eval(expr):
    try:
        c = compile(expr, 'userinput', 'eval')
    except SyntaxError:
        raise ValueError(f"Malformed expression: {expr}")
    m = _re_simple_eval.fullmatch(c.co_code)
    if not m:
        raise ValueError(f"Not a simple algebraic expression: {expr}")
    try:
        return c.co_consts[int.from_bytes(m.group(1), sys.byteorder)]
    except IndexError:
        raise ValueError(f"Expression not evaluated as constant: {expr}")

다른 답변의 예를 사용하여 테스트:

for expr, res in (
    ('2^4',                         6      ),
    ('2**4',                       16      ),
    ('1 + 2*3**(4^5) / (6 + -7)',  -5.0    ),
    ('7 + 9 * (2 << 2)',           79      ),
    ('6 // 2 + 0.0',                3.0    ),
    ('2+3',                         5      ),
    ('6+4/2*2',                    10.0    ),
    ('3+2.45/8',                    3.30625),
    ('3**3*3/3+3',                 30.0    ),
):
    result = simple_eval(expr)
    ok = (result == res and type(result) == type(res))
    print("{} {} = {}".format("OK!" if ok else "FAIL!", expr, result))
OK! 2^4 = 6
OK! 2**4 = 16
OK! 1 + 2*3**(4^5) / (6 + -7) = -5.0
OK! 7 + 9 * (2 << 2) = 79
OK! 6 // 2 + 0.0 = 3.0
OK! 2+3 = 5
OK! 6+4/2*2 = 10.0
OK! 3+2.45/8 = 3.30625
OK! 3**3*3/3+3 = 30.0

잘못된 입력 테스트 중:

for expr in (
    'foo bar',
    'print("hi")',
    '2*x',
    'lambda: 10',
    '2**1234',
):
    try:
        result = simple_eval(expr)
    except ValueError as e:
        print(e)
        continue
    print("OK!")  # will never happen
Malformed expression: foo bar
Not a simple algebraic expression: print("hi")
Expression not evaluated as constant: 2*x
Expression not evaluated as constant: lambda: 10
Expression not evaluated as constant: 2**1234

는 제생저는을 할 것 .eval()하지만 먼저 문자열이 악의적인 것과는 반대로 유효한 수학적 표현인지 확인합니다.정규식을 사용하여 검증할 수 있습니다.

eval()또한 에서는 보안을 강화하기 위해 작동하는 네임스페이스를 제한하는 데 사용할 수 있는 추가 인수를 사용합니다.

[이것이 오래된 질문이라는 것은 알지만, 새로운 유용한 솔루션이 나타나면 지적할 가치가 있습니다.]

python 3.6 이후, 이 기능은 이제 "f-strings"라는 신조어인 언어에 내장되어 있습니다.

참조: PEP 498 -- 리터럴 문자열 보간

예(접두사 참고):

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

이것은 매우 늦은 답변이지만, 저는 향후 참조에 유용하다고 생각합니다.자신만의 수학 파서를 작성하는 대신(위의 파이파싱 예제가 훌륭하지만) 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

정말 멋져요!afrom sympy import * 함수,함수 더 지원을 trig 함수, 특수등같함은과여, 는지만하씬공기엇서무이어, 디서는오지그위다피니것보을주여습기했해제은능원을많기지더훨수.

사용하다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

여기 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])
            )

해결책파이의

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)

종달새 파서 라이브러리 사용하기 https://stackoverflow.com/posts/67491514/edit

from operator import add, sub, mul, truediv, neg, pow
from lark import Lark, Transformer, v_args

calc_grammar = f"""
    ?start: sum
    ?sum: product
        | sum "+" product   -> {add.__name__}
        | sum "-" product   -> {sub.__name__}
    ?product: power
        | product "*" power  -> {mul.__name__}
        | product "/" power  -> {truediv.__name__}
    ?power: atom
        | power "^" atom -> {pow.__name__}
    ?atom: NUMBER           -> number
         | "-" atom         -> {neg.__name__}
         | "(" sum ")"

    %import common.NUMBER
    %import common.WS_INLINE

    %ignore WS_INLINE
"""


@v_args(inline=True)
class CalculateTree(Transformer):
    add = add
    sub = sub
    neg = neg
    mul = mul
    truediv = truediv
    pow = pow
    number = float


calc_parser = Lark(calc_grammar, parser="lalr", transformer=CalculateTree())
calc = calc_parser.parse


def eval_expr(expression: str) -> float:
    return calc(expression)


print(eval_expr("2^4"))
print(eval_expr("-1*2^4"))
print(eval_expr("-2^3 + 1"))
print(eval_expr("2**4"))  # Error

저도 수학 표현 파서를 찾으러 왔습니다.몇 가지 답을 읽고 도서관을 찾아보다가 지금 사용하고 있는 파이 표현을 발견했습니다.기본적으로 많은 연산자와 수식 구조를 처리하지만 누락된 연산자/함수를 쉽게 추가할 수 있습니다.

기본 구문은 다음과 같습니다.

from py_expression.core import Exp
exp = Exp()

parsed_formula = exp.parse('a+4')

result = exp.eval(parsed_formula, {"a":2})

제가 지금까지 가지고 있던 유일한 문제는 수학적 상수나 그것들을 추가하는 메커니즘이 내장되어 있지 않다는 것입니다.하지만 저는 그것에 대한 해결책을 제안했을 뿐입니다: https://github.com/FlavioLionelRita/py-expression/issues/7

언급URL : https://stackoverflow.com/questions/2371436/evaluating-a-mathematical-expression-in-a-string

반응형