Đánh giá một biểu thức toán học trong một chuỗi


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

Điều này trả về lỗi sau:

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

Tôi biết điều đó evalcó thể giải quyết vấn đề này, nhưng không có phương pháp nào tốt hơn và - quan trọng hơn - an toàn hơn để đánh giá một biểu thức toán học đang được lưu trữ trong một chuỗi?


6
^ là toán tử XOR. Giá trị mong đợi là 6. Bạn có thể muốn pow (2,4).
kgiannakakis

25
hoặc nhiều hơn trăn 2 ** 4
fortran

1
Nếu bạn không muốn sử dụng eval, thì giải pháp duy nhất là triển khai trình phân tích ngữ pháp thích hợp. Hãy xem pyparsing .
kgiannakakis

Câu trả lời:


108

Pyparsing có thể được sử dụng để phân tích cú pháp các biểu thức toán học. Đặc biệt, fourFn.py hiển thị cách phân tích cú pháp các biểu thức số học cơ bản. Dưới đây, tôi đã gói lại FourFn thành một lớp phân tích cú pháp số để sử dụng lại dễ dàng hơn.

from __future__ import division
from pyparsing import (Literal, CaselessLiteral, Word, Combine, Group, Optional,
                       ZeroOrMore, Forward, nums, alphas, oneOf)
import math
import operator

__author__ = 'Paul McGuire'
__version__ = '$Revision: 0.0 $'
__date__ = '$Date: 2009-03-20 $'
__source__ = '''http://pyparsing.wikispaces.com/file/view/fourFn.py
http://pyparsing.wikispaces.com/message/view/home/15549426
'''
__note__ = '''
All I've done is rewrap Paul McGuire's fourFn.py as a class, so I can use it
more easily in other places.
'''


class NumericStringParser(object):
    '''
    Most of this code comes from the fourFn.py pyparsing example

    '''

    def pushFirst(self, strg, loc, toks):
        self.exprStack.append(toks[0])

    def pushUMinus(self, strg, loc, toks):
        if toks and toks[0] == '-':
            self.exprStack.append('unary -')

    def __init__(self):
        """
        expop   :: '^'
        multop  :: '*' | '/'
        addop   :: '+' | '-'
        integer :: ['+' | '-'] '0'..'9'+
        atom    :: PI | E | real | fn '(' expr ')' | '(' expr ')'
        factor  :: atom [ expop factor ]*
        term    :: factor [ multop factor ]*
        expr    :: term [ addop term ]*
        """
        point = Literal(".")
        e = CaselessLiteral("E")
        fnumber = Combine(Word("+-" + nums, nums) +
                          Optional(point + Optional(Word(nums))) +
                          Optional(e + Word("+-" + nums, nums)))
        ident = Word(alphas, alphas + nums + "_$")
        plus = Literal("+")
        minus = Literal("-")
        mult = Literal("*")
        div = Literal("/")
        lpar = Literal("(").suppress()
        rpar = Literal(")").suppress()
        addop = plus | minus
        multop = mult | div
        expop = Literal("^")
        pi = CaselessLiteral("PI")
        expr = Forward()
        atom = ((Optional(oneOf("- +")) +
                 (ident + lpar + expr + rpar | pi | e | fnumber).setParseAction(self.pushFirst))
                | Optional(oneOf("- +")) + Group(lpar + expr + rpar)
                ).setParseAction(self.pushUMinus)
        # by defining exponentiation as "atom [ ^ factor ]..." instead of
        # "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
        # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
        factor = Forward()
        factor << atom + \
            ZeroOrMore((expop + factor).setParseAction(self.pushFirst))
        term = factor + \
            ZeroOrMore((multop + factor).setParseAction(self.pushFirst))
        expr << term + \
            ZeroOrMore((addop + term).setParseAction(self.pushFirst))
        # addop_term = ( addop + term ).setParseAction( self.pushFirst )
        # general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
        # expr <<  general_term
        self.bnf = expr
        # map operator symbols to corresponding arithmetic operations
        epsilon = 1e-12
        self.opn = {"+": operator.add,
                    "-": operator.sub,
                    "*": operator.mul,
                    "/": operator.truediv,
                    "^": operator.pow}
        self.fn = {"sin": math.sin,
                   "cos": math.cos,
                   "tan": math.tan,
                   "exp": math.exp,
                   "abs": abs,
                   "trunc": lambda a: int(a),
                   "round": round,
                   "sgn": lambda a: abs(a) > epsilon and cmp(a, 0) or 0}

    def evaluateStack(self, s):
        op = s.pop()
        if op == 'unary -':
            return -self.evaluateStack(s)
        if op in "+-*/^":
            op2 = self.evaluateStack(s)
            op1 = self.evaluateStack(s)
            return self.opn[op](op1, op2)
        elif op == "PI":
            return math.pi  # 3.1415926535
        elif op == "E":
            return math.e  # 2.718281828
        elif op in self.fn:
            return self.fn[op](self.evaluateStack(s))
        elif op[0].isalpha():
            return 0
        else:
            return float(op)

    def eval(self, num_string, parseAll=True):
        self.exprStack = []
        results = self.bnf.parseString(num_string, parseAll)
        val = self.evaluateStack(self.exprStack[:])
        return val

Bạn có thể sử dụng nó như thế này

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

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

180

eval là ác

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

Lưu ý: ngay cả khi bạn sử dụng set __builtins__thành Nonenó vẫn có thể thoát ra bằng cách sử dụng xem xét nội tâm:

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

Đánh giá biểu thức số học bằng cách sử dụng 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)

Bạn có thể dễ dàng giới hạn phạm vi cho phép cho mỗi thao tác hoặc bất kỳ kết quả trung gian nào, ví dụ: để giới hạn các đối số đầu vào cho 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

Hoặc để hạn chế độ lớn của các kết quả trung gian:

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

Thí dụ

>>> 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
Bài viết rất hay, cảm ơn. Tôi đã lấy khái niệm đó, và cố gắng để tạo ra một thư viện mà nên dễ sử dụng: github.com/danthedeckie/simpleeval
Daniel Fairhead

điều này có thể được mở rộng cho các chức năng của import math?
Hotschke

2
Lưu ý rằng ast.parsekhông an toàn. Ví dụ: ast.parse('()' * 1000000, '<string>', 'single')sự cố trình thông dịch.
Antti Haapala

1
@AnttiHaapala là một ví dụ điển hình. Nó có phải là một lỗi trong trình thông dịch Python không? Dù sao, đầu vào lớn được xử lý nhẹ nhàng, ví dụ: sử dụng if len(expr) > 10000: raise ValueError.
jfs

1
@AnttiHaapala bạn có thể cung cấp một ví dụ không thể sửa được bằng len(expr)séc không? Hoặc quan điểm của bạn là có lỗi trong quá trình triển khai Python và do đó không thể viết mã an toàn nói chung?
jfs

13

Một số lựa chọn thay thế an toàn hơn cho eval()* :sympy.sympify().evalf()

* SymPy sympifycũng không an toàn theo cảnh báo sau đây từ tài liệu.

Cảnh báo: Lưu ý rằng chức năng này sử dụng evalvà do đó không nên sử dụng trên đầu vào không được vệ sinh.


10

Được rồi, vì vậy vấn đề với eval là nó có thể thoát khỏi hộp cát quá dễ dàng, ngay cả khi bạn thoát khỏi __builtins__. Tất cả các phương pháp để thoát hộp cát đều sử dụng getattrhoặc object.__getattribute__(thông qua .toán tử) để lấy tham chiếu đến một số đối tượng nguy hiểm thông qua một số đối tượng được phép ( ''.__class__.__bases__[0].__subclasses__hoặc tương tự). getattrbị loại bỏ bằng cách đặt __builtins__thành None. object.__getattribute__là một khó khăn, vì nó không thể đơn giản được gỡ bỏ, cả vì objectnó là bất biến và bởi vì loại bỏ nó sẽ phá vỡ mọi thứ. Tuy nhiên, __getattribute__chỉ có thể truy cập thông qua .nhà điều hành, vì vậy việc loại bỏ dữ liệu đó khỏi đầu vào của bạn là đủ để đảm bảo eval không thể thoát khỏi hộp cát của nó.
Trong các công thức xử lý, cách sử dụng hợp lệ duy nhất của số thập phân là khi nó đứng trước hoặc theo sau bởi[0-9], vì vậy chúng tôi chỉ xóa tất cả các trường hợp khác của ..

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

Lưu ý rằng trong khi python thường xử lý 1 + 1.như vậy 1 + 1.0, điều này sẽ xóa dấu vết .và để lại cho bạn 1 + 1. Bạn có thể thêm ), EOFvào danh sách những thứ được phép làm theo ., nhưng tại sao bận tâm?


Một câu hỏi liên quan với cuộc thảo luận thú vị có thể được tìm thấy ở đây .
djvg

3
Cho dù lập luận về việc loại bỏ .hiện tại có đúng hay không , điều này có khả năng xảy ra các lỗ hổng bảo mật nếu các phiên bản Python trong tương lai đưa ra cú pháp mới cho phép các đối tượng hoặc chức năng không an toàn được truy cập theo một cách nào đó. Giải pháp này đã không an toàn bằng Python 3.6 vì e-dây, cho phép tấn công sau: f"{eval('()' + chr(46) + '__class__')}". Một giải pháp dựa trên danh sách trắng thay vì danh sách đen sẽ an toàn hơn, nhưng thực sự tốt hơn là giải quyết vấn đề này mà không cần eval.
kaya3

Đó là một điểm tuyệt vời về các tính năng ngôn ngữ trong tương lai giới thiệu các vấn đề bảo mật mới.
Perkins

8

Bạn có thể sử dụng mô-đun ast và viết một NodeVisitor để xác minh rằng loại của mỗi nút là một phần của danh sách trắng.

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)

Vì nó hoạt động thông qua danh sách trắng chứ không phải danh sách đen nên nó an toàn. Các hàm và biến duy nhất mà nó có thể truy cập là những hàm mà bạn cấp cho nó một cách rõ ràng. Tôi đã điền một mệnh lệnh với các hàm liên quan đến toán học để bạn có thể dễ dàng cung cấp quyền truy cập vào các hàm đó nếu bạn muốn, nhưng bạn phải sử dụng nó một cách rõ ràng.

Nếu chuỗi cố gắng gọi các hàm chưa được cung cấp hoặc gọi bất kỳ phương thức nào, một ngoại lệ sẽ được đưa ra và nó sẽ không được thực thi.

Bởi vì điều này sử dụng trình phân tích cú pháp và đánh giá được xây dựng trong Python, nó cũng kế thừa các quy tắc ưu tiên và thăng hạng của Python.

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

Đoạn mã trên chỉ mới được thử nghiệm trên Python 3.

Nếu muốn, bạn có thể thêm bộ trang trí thời gian chờ trên chức năng này.


7

Lý do evalexecrất nguy hiểm là compilehàm mặc định sẽ tạo ra bytecode cho bất kỳ biểu thức python hợp lệ nào và mặc định evalhoặc execsẽ thực thi bất kỳ bytecode của python hợp lệ nào. Tất cả các câu trả lời cho đến nay đều tập trung vào việc hạn chế mã bytecode có thể được tạo (bằng cách làm sạch đầu vào) hoặc xây dựng ngôn ngữ miền cụ thể của riêng bạn bằng AST.

Thay vào đó, bạn có thể dễ dàng tạo một evalhàm đơn giản không có khả năng làm bất cứ điều gì bất chính và có thể dễ dàng kiểm tra thời gian chạy trên bộ nhớ hoặc thời gian được sử dụng. Tất nhiên, nếu nó là một phép toán đơn giản, thì có một phím tắt.

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]

Cách hoạt động của điều này rất đơn giản, bất kỳ biểu thức toán học hằng số nào đều được đánh giá an toàn trong quá trình biên dịch và được lưu trữ dưới dạng hằng số. Đối tượng mã được trả về bởi trình biên dịch bao gồm d, là mã bytecode cho LOAD_CONST, theo sau là số của hằng số cần tải (thường là đối tượng cuối cùng trong danh sách), tiếp theo Slà mã bytecode RETURN_VALUE. Nếu phím tắt này không hoạt động, điều đó có nghĩa là đầu vào của người dùng không phải là một biểu thức hằng số (chứa một biến hoặc lệnh gọi hàm hoặc tương tự).

Điều này cũng mở ra cánh cửa cho một số định dạng đầu vào phức tạp hơn. Ví dụ:

stringExp = "1 + cos(2)"

Điều này yêu cầu thực sự đánh giá bytecode, vẫn còn khá đơn giản. Python bytecode là một ngôn ngữ hướng ngăn xếp, vì vậy mọi thứ đều là một vấn đề đơn giản TOS=stack.pop(); op(TOS); stack.put(TOS)hoặc tương tự. Điều quan trọng là chỉ triển khai các mã opcode an toàn (tải / lưu trữ giá trị, phép toán, trả về giá trị) chứ không phải những mã không an toàn (tra cứu thuộc tính). Nếu bạn muốn người dùng có thể gọi các hàm (toàn bộ lý do không sử dụng phím tắt ở trên), hãy đơn giản thực hiện việc CALL_FUNCTIONchỉ cho phép các hàm trong danh sách 'an toàn'.

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

Rõ ràng, phiên bản thực của điều này sẽ dài hơn một chút (có 119 opcode, 24 trong số đó liên quan đến toán học). Việc thêm STORE_FASTvà một vài người khác sẽ cho phép đầu vào giống như 'x=5;return x+xhoặc tương tự, dễ dàng. Nó thậm chí có thể được sử dụng để thực thi các chức năng do người dùng tạo, miễn là các chức năng do người dùng tạo tự thực thi thông qua VMeval (đừng làm cho chúng có thể gọi được !!! hoặc chúng có thể được sử dụng như một lệnh gọi lại ở đâu đó). Việc xử lý các vòng lặp yêu cầu hỗ trợ các gotomã byte, có nghĩa là thay đổi từ một fortrình vòng lặp sang whilevà duy trì một con trỏ đến lệnh hiện tại, nhưng không quá khó. Đối với khả năng chống lại DOS, vòng lặp chính nên kiểm tra thời gian đã trôi qua kể từ khi bắt đầu tính toán và một số toán tử nhất định nên từ chối đầu vào vượt quá một số giới hạn hợp lý (BINARY_POWER là rõ ràng nhất).

Mặc dù cách tiếp cận này dài hơn một chút so với trình phân tích cú pháp ngữ pháp đơn giản cho các biểu thức đơn giản (xem ở trên về việc chỉ lấy hằng số đã biên dịch), nhưng nó mở rộng dễ dàng đến các đầu vào phức tạp hơn và không yêu cầu xử lý ngữ pháp ( compilelấy bất cứ thứ gì phức tạp tùy ý và giảm nó thành một chuỗi các hướng dẫn đơn giản).


6

Tôi nghĩ rằng tôi sẽ sử dụng eval(), nhưng trước tiên sẽ kiểm tra để đảm bảo rằng chuỗi là một biểu thức toán học hợp lệ, trái ngược với một cái gì đó độc hại. Bạn có thể sử dụng regex để xác nhận.

eval() cũng có các đối số bổ sung mà bạn có thể sử dụng để hạn chế vùng tên mà nó hoạt động nhằm bảo mật cao hơn.


3
Tuy nhiên, tất nhiên, đừng dựa vào các biểu thức chính quy để xác nhận các biểu thức toán học tùy ý.
hiệu suất cao

@ High-Performance Mark: Vâng, tôi đoán nó phụ thuộc vào loại biểu thức toán học mà anh ấy nghĩ đến. . . ví dụ, chỉ số học đơn giản với những con số và +, -, *, /, **, (, )hoặc một cái gì đó phức tạp hơn
Tim Goodman

@Tim - đó là () tôi lo lắng, hay đúng hơn là (((((()))))). Thực ra, tôi nghĩ OP nên lo lắng về họ, lông mày của tôi không khỏi nhăn lại vì vấn đề của OP.
hiệu suất cao

2
Không sử dụng eval()nếu bạn không kiểm soát đầu vào ngay cả khi bạn hạn chế không gian tên, ví dụ, eval("9**9**9**9**9**9**9**9", {'__builtins__': None})tiêu thụ CPU, bộ nhớ.
jfs

3
Hạn chế không gian tên của eval không tăng thêm tính bảo mật .
Antti Haapala

5

Đây là một câu trả lời rất muộn, nhưng tôi nghĩ hữu ích để tham khảo trong tương lai. Thay vì viết trình phân tích cú pháp toán học của riêng bạn (mặc dù ví dụ pyparsing ở trên rất tuyệt), bạn có thể sử dụng SymPy. Tôi không có nhiều kinh nghiệm về nó, nhưng nó chứa một công cụ toán học mạnh mẽ hơn nhiều so với bất kỳ ai có thể viết cho một ứng dụng cụ thể và việc đánh giá biểu thức cơ bản rất dễ dàng:

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

Quả thực rất tuyệt! A from sympy import *mang lại nhiều hỗ trợ chức năng hơn, chẳng hạn như hàm cắt, chức năng đặc biệt, v.v., nhưng tôi đã tránh điều đó ở đây để hiển thị những gì đến từ đâu.


3
Giao hưởng có "an toàn" không? Có vẻ như có rất nhiều bài đăng cho rằng nó là một trình bao bọc xung quanh eval () có thể được khai thác theo cách tương tự. Cũng evalfkhông lấy ndarrays numpy.
Mark Mikofski

14
Không có bản giao hưởng nào là không an toàn cho đầu vào không đáng tin cậy. Hãy thử sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")cuộc gọi này subprocess.Popen()mà tôi đã chuyển lsthay vì rm -rf /. Chỉ số có thể sẽ khác trên các máy tính khác. Đây là một biến thể của Ned Batchelder khai thác
Đánh dấu Mikofski

1
Thật vậy, nó không tăng thêm an toàn chút nào.
Antti Haapala

4

[Tôi biết đây là một câu hỏi cũ, nhưng đáng để chỉ ra các giải pháp hữu ích mới khi chúng bật lên]

Kể từ python3.6, khả năng này hiện đã được tích hợp vào ngôn ngữ , được đặt ra là "f-string" .

Xem: PEP 498 - Nội suy chuỗi chữ

Ví dụ (lưu ý ftiền tố):

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

7
Liên kết rất thú vị. Nhưng tôi đoán f-string ở đây để giúp việc viết mã nguồn dễ dàng hơn, trong khi câu hỏi dường như là về cách làm việc với các chuỗi bên trong các biến (có thể từ các nguồn không đáng tin cậy). f-string không thể được sử dụng trong trường hợp đó.
Bernhard

là có cách nào để làm điều gì đó để tác động của f '{{2} điều hành 4}', nơi bây giờ bạn có thể gán các nhà điều hành để làm 2 + 4 hoặc 2 * 4 hoặc 2-4 hoặc vv
Skyler

Điều này thực tế tương đương với việc chỉ làm str(eval(...)), vì vậy nó chắc chắn không an toàn hơn eval.
kaya3

Có vẻ giống nhau với
executive

0

Sử dụng evaltrong một không gian tên sạch:

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

Không gian tên sạch sẽ ngăn chặn việc tiêm. Ví dụ:

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

Nếu không, bạn sẽ nhận được:

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

Bạn có thể muốn cấp quyền truy cập vào mô-đun toán học:

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

35
eval ("(1) .__ class __.__ base __ [0] .__ subclasses __ () [81] ('echo got through'.split ())", {' builtins ': None}) #escapes sandbox của bạn
Perkins

6
Python 3.4: Thực 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})thi shell bourne ...
Antti Haapala

8
Điều này không an toàn . Mã độc hại vẫn có thể được thực thi.
Nghịch lý Fermi

This is not safe- tốt, tôi nghĩ nó an toàn như sử dụng bash nói chung. BTW: eval('math.sqrt(2.0)')<- "toán học." được yêu cầu như đã viết ở trên.
Hannu

0

Đây là giải pháp của tôi cho vấn đề mà không cần sử dụng eval. Hoạt động với Python2 và Python3. Nó không hoạt động với số âm.

$ 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.