문자열에서 수학 표현식 평가


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입니다. pow (2,4)를 원할 것입니다.
kgiannakakis

25
또는 그 이상 비단뱀 2 ** 4
fortran

1
eval을 사용하지 않으려는 경우 유일한 해결책은 적절한 문법 파서를 구현하는 것입니다. pyparsing을 살펴보십시오 .
kgiannakakis

답변:


108

Pyparsing 을 사용하여 수학적 표현을 구문 분석 할 수 있습니다. 특히 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

참고 : set __builtins__를 사용하더라도 Noneintrospection을 사용하여 중단 할 수 있습니다.

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
다니엘 Fairhead

이 기능을 확장 할 수 import math있습니까?
Hotschke 2014

2
즉 참고 ast.parse안전하지 않습니다. 예를 들어 ast.parse('()' * 1000000, '<string>', 'single')인터프리터가 충돌합니다.
안티 Haapala

1
@AnttiHaapala 좋은 예입니다. 파이썬 인터프리터의 버그입니까? 어쨌든 큰 입력은 예를 들어 if len(expr) > 10000: raise ValueError.
jfs

1
@AnttiHaapala 수표로 수정할 수없는 예를 제공 할 수 len(expr)있습니까? 아니면 파이썬 구현에 버그가있어서 일반적으로 안전한 코드를 작성하는 것이 불가능하다는 것이 요점입니까?
jfs


10

좋습니다. eval의 문제는 .NET을 제거하더라도 너무 쉽게 샌드 박스를 벗어날 수 있다는 것입니다 __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})

참고 파이썬 일반적으로 치료가 동안이 1 + 1.아니라 1 + 1.0,이 후행를 제거 .하고 당신을 떠나 1 + 1. 당신은 추가 할 수 있습니다 ), 그리고 EOF수행 할 수 것들의 목록 ., 그런데 왜 귀찮게?


흥미로운 토론이있는 관련 질문은 여기 에서 찾을 수 있습니다 .
djvg

3
제거에 대한 주장 .이 현재 올바른지 여부에 관계없이 향후 Python 버전에서 안전하지 않은 객체 또는 함수에 다른 방식으로 액세스 할 수있는 새로운 구문을 도입하면 보안 취약성이 발생할 가능성이 있습니다. 이 솔루션은 다음 공격을 허용하는 f- 문자열 때문에 Python 3.6에서 이미 안전하지 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)

블랙리스트가 아닌 화이트리스트를 통해 작동하기 때문에 안전합니다. 액세스 할 수있는 유일한 함수와 변수는 명시 적으로 액세스 권한을 부여한 것입니다. 원하는 경우 쉽게 액세스 할 수 있도록 수학 관련 함수로 dict를 채웠지만 명시 적으로 사용해야합니다.

문자열이 제공되지 않은 함수를 호출하거나 메서드를 호출하려고하면 예외가 발생하고 실행되지 않습니다.

이것은 Python의 내장 파서 및 평가기를 사용하기 때문에 Python의 우선 순위 및 승격 규칙도 상속합니다.

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

위의 코드는 Python 3에서만 테스트되었습니다.

원하는 경우이 함수에 타임 아웃 데코레이터를 추가 할 수 있습니다.


7

그 이유 evalexec그렇게 위험은 기본이다 compile기능은 유효한 파이썬 표현을위한 바이트 코드, 기본을 생성 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]

이것이 작동하는 방식은 간단합니다. 상수 수학 식은 컴파일 중에 안전하게 평가되고 상수로 저장됩니다. compile에 의해 반환되는 코드 객체 d는에 대한 바이트 코드 인 LOAD_CONST,로드 할 상수 (일반적으로 목록의 마지막 항목) 번호, S에 대한 바이트 코드 인으로 구성됩니다 RETURN_VALUE. 이 단축키가 작동하지 않으면 사용자 입력이 상수 표현식이 아님을 의미합니다 (변수 또는 함수 호출 등을 포함 함).

이것은 또한 좀 더 정교한 입력 형식에 대한 문을 열어줍니다. 예를 들면 :

stringExp = "1 + cos(2)"

이를 위해서는 실제로 바이트 코드를 평가해야하며 이는 여전히 매우 간단합니다. Python 바이트 코드는 스택 지향 언어이므로 모든 것이 단순 TOS=stack.pop(); op(TOS); stack.put(TOS)하거나 유사합니다. 핵심은 안전하고 (값로드 / 저장, 수학 연산, 값 반환) 안전하지 않은 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 개의 opcode가 있고 그 중 24 개는 수학과 관련이 있습니다). 추가 STORE_FAST및 몇 가지 다른 것은 'x=5;return x+x사소하게 쉽게 유사하거나 유사한 입력을 허용합니다 . 사용자가 만든 함수가 VMeval을 통해 자체적으로 실행되는 한 사용자가 만든 함수를 실행하는 데 사용할 수도 있습니다 (호출 가능하게 만들지 마세요 !!! 또는 어딘가에서 콜백으로 사용될 수 있음). 루프를 처리하려면 goto바이트 코드에 대한 지원이 필요합니다. 즉, for반복자 while에서 현재 명령어로의 포인터를 변경 하고 유지하는 것을 의미 하지만 너무 어렵지는 않습니다. DOS에 대한 저항을 위해 메인 루프는 계산 시작 이후 얼마나 많은 시간이 경과했는지 확인해야하며 특정 연산자는 합리적인 제한 (BINARY_POWER 가장 명백한 것).

이 접근 방식은 간단한 표현을위한 단순한 문법 파서보다 다소 길지만 (위의 컴파일 된 상수를 잡는 것에 대한 위 참조) 더 복잡한 입력으로 쉽게 확장되고 문법을 다룰 필요가 없습니다 ( compile임의로 복잡한 것을 취하고 일련의 간단한 지침).


6

나는을 사용할 것이라고 생각 eval()하지만 먼저 문자열이 악의적 인 것이 아닌 유효한 수학적 표현인지 확인합니다. 유효성 검사에 정규식을 사용할 수 있습니다.

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


3
그러나 물론 임의의 수학 식을 검증하기 위해 정규식에 의존하지 마십시오.
High Performance Mark

@ High-Performance Mark : 예, 그가 염두에두고있는 수학적 표현의 종류에 따라 다릅니다. . . 예를 들어, 단순한 숫자 연산과 +, -, *, /, **, (, )또는 뭔가 더 복잡
팀 굿맨을

@Tim-내가 걱정하는 ()이거나 오히려 (((((()))))))입니다. 사실 OP는 그들에 대해 걱정해야한다고 생각하고, 내 눈썹은 OP의 문제로 인해 풀렸다.
High Performance Mark

2
eval()예를 들어 eval("9**9**9**9**9**9**9**9", {'__builtins__': None})CPU, 메모리 소모와 같이 네임 스페이스를 제한하더라도 입력을 제어하지 않는 경우 사용하지 마십시오 .
JFS

3
eval의 네임 스페이스를 제한 해도 보안이 추가되지는 않습니다 .
Antti Haapala 2011 년

5

이것은 엄청나게 늦은 답변이지만 향후 참조에 유용하다고 생각합니다. 자신의 수학 파서를 작성하는 대신 (위의 pyparsing 예제가 훌륭하지만) 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
sympy는 "안전"합니까? 동일한 방식으로 악용 될 수있는 eval ()을 둘러싼 래퍼라고 제안하는 많은 게시물 이있는 것 같습니다 . 또한 evalfnumpy ndarrays를 사용하지 않습니다.
Mark Mikofski 2013 년

14
신뢰할 수없는 입력에는 sympy가 안전하지 않습니다. 대신 전달한 sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")이 호출 subprocess.Popen()을 시도하십시오 . 다른 컴퓨터에서는 인덱스가 다를 수 있습니다. 이것은의 변종이다 악용 네드 BATCHELDERlsrm -rf /
마크 Mikofski

1
사실, 그것은 안전을 전혀 추가하지 않습니다.
Antti Haapala 2011 년

4

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

python3.6 이후,이 기능은 이제 "f-strings" 라는 언어로 빌드되었습니다. .

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

예 ( f접두사 참고 ) :

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

7
매우 흥미로운 링크입니다. 그러나 나는 소스 코드를 더 쉽게 작성하기 위해 f- 문자열이 여기에 있다고 생각하지만 질문은 변수 내부의 문자열 작업에 관한 것 같습니다 (신뢰할 수없는 소스에서 가져온 것 같습니다). 이 경우 f- 문자열을 사용할 수 없습니다.
Bernhard

f '{2 {operator} 4}'의 효과에 대해 뭔가를 할 수있는 방법이 있습니까? 이제 연산자에게 2 + 4 또는 2 * 4 또는 2-4 등을 할당 할 수 있습니다
Skyler

이것은 그냥하는 것과 거의 동일 str(eval(...))하므로 확실히보다 안전하지 않습니다 eval.
kaya3

exec / eval과 동일한 것 같습니다 ...
Victor VosMottor 감사합니다 Monica

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) .__ class __.__ bases __ [0] .__ subclasses __ () [81] ( 'echo got through'.split ())", {' builtins ': None}) #escapes your sandbox
Perkins

6
파이썬 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})... Bourne 쉘을 실행
안티 Haapala

8
이것은 안전하지 않습니다 . 악성 코드는 여전히 실행될 수 있습니다.
Fermi paradox

This is not safe-글쎄요, 전체적으로 bash를 사용하는 것만 큼 안전하다고 생각합니다. BTW : eval('math.sqrt(2.0)')<- "수학." 위에 쓰여진대로 필요합니다.
Hannu 2017

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

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.