एक स्ट्रिंग में गणितीय अभिव्यक्ति का मूल्यांकन


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) चाहते हैं।
kincnakakis

25
या अधिक अजगर 2 ** 4
फोरट्रान

1
यदि आप eval का उपयोग नहीं करना चाहते हैं, तो उचित व्याकरण पार्सर को लागू करना एकमात्र उपाय है। झांक कर देखो ।
किंजनाककिस

जवाबों:


108

गणितीय अभिव्यक्तियों को पार्स करने के लिए Pyparsing का उपयोग किया जा सकता है। विशेष रूप से, चारFn.py दिखाता है कि बुनियादी अंकगणितीय अभिव्यक्तियों को कैसे पार्स करें। नीचे, मैंने आसानी से पुन: उपयोग के लिए एक संख्यात्मक पार्सर वर्ग में फोरफ़न को फिर से जोड़ दिया है।

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
डैनियल फेयरहेड

के कार्यों के लिए इसे बढ़ाया जा सकता है import math?
होट्सच जूल

2
ध्यान दें कि ast.parseसुरक्षित नहीं है। उदाहरण के लिए ast.parse('()' * 1000000, '<string>', 'single')दुभाषिया क्रैश।
अंती हापला

1
@AnttiHaapala अच्छा उदाहरण है। क्या यह पायथन इंटरप्रेटर में एक बग है? वैसे भी, बड़े इनपुट को तुच्छ रूप से संभाला जाता है, जैसे, उपयोग करना if len(expr) > 10000: raise ValueError
jfs

1
@AnttiHaapala क्या आप एक उदाहरण प्रदान कर सकते हैं जो len(expr)चेक का उपयोग करके तय नहीं किया जा सकता है ? या आपकी बात यह है कि पायथन कार्यान्वयन में कीड़े हैं और इसलिए सामान्य रूप से सुरक्षित कोड लिखना असंभव है?
jfs

13

कुछ सुरक्षित विकल्प eval()और * :sympy.sympify().evalf()

*sympify प्रलेखन से निम्नलिखित चेतावनी के अनुसार SymPy भी असुरक्षित है।

चेतावनी: ध्यान दें कि यह फ़ंक्शन उपयोग करता है eval, और इस प्रकार इसका उपयोग अनियंत्रित इनपुट पर नहीं किया जाना चाहिए।


10

ठीक है, इसलिए eval के साथ समस्या यह है कि यह अपने सैंडबॉक्स से भी आसानी से बच सकता है, भले ही आप छुटकारा पा लें __builtins__। सैंडबॉक्स से बचने के सभी तरीके किसी अनुमति प्राप्त वस्तु ( या समान) के माध्यम से किसी खतरनाक वस्तु का संदर्भ प्राप्त करने के लिए ( getattrया ऑपरेटर के object.__getattribute__माध्यम से .) नीचे आते हैं ''.__class__.__bases__[0].__subclasses__getattrद्वारा सेटिंग __builtins__को समाप्त कर दिया गया है Noneobject.__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
हटाने के बारे में तर्क .फिलहाल सही है या नहीं , यह सुरक्षा कमजोरियों की संभावना को छोड़ देता है यदि भविष्य के संस्करणों में नए ऑब्जेक्ट्स या फ़ंक्शन को किसी अन्य तरीके से एक्सेस करने की अनुमति देने वाले नए सिंटैक्स को पेश किया जाए। एफ-स्ट्रिंग्स के कारण पायथन 3.6 में यह समाधान पहले से ही असुरक्षित है, जो निम्नलिखित हमले की अनुमति देता है f"{eval('()' + chr(46) + '__class__')}":। ब्लैक लिस्ट करने के बजाय श्वेतसूची पर आधारित एक समाधान सुरक्षित होगा, लेकिन वास्तव में इस समस्या को बिना हल करना बेहतर है eval
काया ३

यह भविष्य की भाषा सुविधाओं के बारे में एक उत्कृष्ट बिंदु है जो नए सुरक्षा मुद्दों को पेश करता है।
पर्किन्स

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)

क्योंकि यह ब्लैकलिस्ट के बजाय व्हाइटलिस्ट के माध्यम से काम करता है, इसलिए यह सुरक्षित है। एकमात्र कार्य और चर जो इसे एक्सेस कर सकते हैं वे हैं जिन्हें आप स्पष्ट रूप से इसे एक्सेस देते हैं। मैंने गणित से संबंधित कार्यों के साथ एक तानाशाह को आबाद किया ताकि आप चाहें तो आसानी से उन तक पहुंच प्रदान कर सकें, लेकिन आपको स्पष्ट रूप से इसका उपयोग करना होगा।

यदि स्ट्रिंग ऐसे फ़ंक्शन को कॉल करने का प्रयास करती है जो प्रदान नहीं किए गए हैं, या किसी भी तरीके को लागू करते हैं, तो एक अपवाद उठाया जाएगा, और इसे निष्पादित नहीं किया जाएगा।

चूँकि यह पार्सर और मूल्यांकनकर्ता में निर्मित पायथन का उपयोग करता है, इसलिए यह पायथन की पूर्वता और पदोन्नति नियमों को भी विरासत में मिला है।

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

उपरोक्त कोड केवल पायथन 3 पर परीक्षण किया गया है।

यदि वांछित है, तो आप इस फ़ंक्शन पर टाइमआउट डेकोरेटर जोड़ सकते हैं।


7

कारण evalऔर execइतने खतरनाक हैं कि डिफ़ॉल्ट compileफ़ंक्शन किसी भी मान्य अजगर अभिव्यक्ति के लिए बायटेकोड उत्पन्न करेगा, और डिफ़ॉल्ट evalया execकिसी भी मान्य अजगर बाइटकोड को निष्पादित करेगा। तिथि के सभी उत्तरों ने बायटेकोड को प्रतिबंधित करने पर ध्यान केंद्रित किया है जो उत्पन्न हो सकता है (इनपुट को सैनिटाइज़ करके) या एएसटी का उपयोग करके अपने स्वयं के डोमेन-विशिष्ट-भाषा का निर्माण कर सकता है।

इसके बजाय, आप आसानी से एक सरल 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)"

इसके लिए वास्तव में बाइटकोड का मूल्यांकन करना आवश्यक है, जो अभी भी काफी सरल है। पायथन बाइटकोड एक स्टैक ओरिएंटेड भाषा है, इसलिए सब कुछ एक 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 और एक संकेतक को वर्तमान निर्देश को बनाए रखना, लेकिन यह अधिक कठिन नहीं है। डॉस के प्रतिरोध के लिए, मुख्य लूप को यह जांचना चाहिए कि गणना शुरू होने के बाद कितना समय बीत चुका है, और कुछ ऑपरेटरों को कुछ उचित सीमा से अधिक इनपुट से इनकार करना चाहिए (BINARY_POWER

हालांकि यह दृष्टिकोण सरल अभिव्यक्तियों के लिए एक साधारण व्याकरण पार्सर की तुलना में कुछ अधिक लंबा है (ऊपर संकलित स्थिरांक को हथियाने के बारे में ऊपर देखें), यह आसानी से अधिक जटिल इनपुट तक फैलता है, और व्याकरण से निपटने की आवश्यकता नहीं है ( compileमनमाने ढंग से जटिल कुछ भी ले लो और इसे कम कर देता है) सरल निर्देशों का एक क्रम)।


6

मुझे लगता है कि मैं उपयोग करूंगा eval(), लेकिन पहले यह सुनिश्चित करने के लिए जांच करूंगा कि स्ट्रिंग एक वैध गणितीय अभिव्यक्ति है, जैसा कि कुछ दुर्भावनापूर्ण है। आप मान्यता के लिए एक regex का उपयोग कर सकते हैं।

eval() अतिरिक्त तर्क भी लेता है जिसका उपयोग आप अधिक सुरक्षा के लिए संचालित नामस्थान को प्रतिबंधित करने के लिए कर सकते हैं।


3
लेकिन, निश्चित रूप से, मनमाने ढंग से गणितीय अभिव्यक्तियों को मान्य करने के लिए नियमित अभिव्यक्तियों पर भरोसा नहीं करते हैं।
उच्च प्रदर्शन मार्क

@ उच्च-प्रदर्शन मार्क: हां, मुझे लगता है कि यह इस बात पर निर्भर करता है कि उसके मन में किस तरह के गणितीय भाव हैं। । । जैसे, संख्या के साथ सिर्फ साधारण अंकगणित और +, -, *, /, **, (, )या कुछ और अधिक जटिल
टिम गुडमैन

@ समय - यह () मैं (या (((()))))) के बारे में चिंतित हूं। सच में, मुझे लगता है कि ओपी को उनके बारे में चिंता करनी चाहिए, मेरी भौंह ओपी की समस्याओं से प्रभावित है।
उच्च प्रदर्शन मार्क

2
eval()यदि आप नाम स्थान जैसे कि प्रतिबंधित करते हैं तो भी इनपुट का उपयोग न करें , उदाहरण के लिए, eval("9**9**9**9**9**9**9**9", {'__builtins__': None})सीपीयू, मेमोरी की खपत करते हैं।
JFS

3
Eval के नाम स्थान को प्रतिबंधित करने से सुरक्षा में कोई इजाफा नहीं होता है
अंती हापाला

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

वास्तव में बहुत अच्छा! एक from sympy import *बहुत अधिक फ़ंक्शन समर्थन में लाता है, जैसे कि ट्रिगर फ़ंक्शंस, विशेष फ़ंक्शन आदि, लेकिन मैंने यह टाल दिया है कि यहां यह दिखाने के लिए कि क्या कहाँ से आ रहा है।


3
क्या हमदर्द "सुरक्षित" है? ऐसा लगता है कि कई पद हैं जो यह सुझाव देते हैं कि यह एक आवरण के आसपास है () जो उसी तरह से शोषण किया जा सकता है। इसके अलावा evalfसुन्न ndarrays नहीं है।
मार्क मिकोफ़्स्की

14
अविश्वासित इनपुट के लिए कोई सहानुभूति सुरक्षित नहीं है। sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")इस कॉल को आज़माएं subprocess.Popen()जो मैंने lsइसके बजाय पास किया rm -rf /। सूचकांक शायद अन्य कंप्यूटरों पर अलग होगा। यह नेड
बैचेल्ड

1
वास्तव में, यह सुरक्षा के लिए बिल्कुल भी नहीं जोड़ता है।
अंती हापला

4

[मुझे पता है कि यह एक पुराना सवाल है, लेकिन यह नए उपयोगी समाधानों को इंगित करने के लायक है क्योंकि वे पॉप अप करते हैं]

Python3.6 के बाद से, इस क्षमता को अब भाषा में बनाया गया है , जिसे "एफ-स्ट्रिंग्स" कहा जाता है

देखें: PEP 498 - शाब्दिक स्ट्रिंग इंटरपोल

उदाहरण के लिए ( fउपसर्ग पर ध्यान दें ):

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

7
बहुत ही रोचक लिंक। लेकिन मुझे लगता है कि लेखन स्रोत कोड को आसान बनाने के लिए एफ-स्ट्रिंग्स यहां हैं, जबकि सवाल चर के अंदर तार के साथ काम करने के बारे में प्रतीत होता है (संभवतः अविश्वसनीय स्रोतों से)। उस स्थिति में f- स्ट्रिंग्स का उपयोग नहीं किया जा सकता है।
बर्नहार्ड

क्या f '{2 {ऑपरेटर} 4}' के प्रभाव के लिए कुछ करने का कोई तरीका है, जहां आप ऑपरेटर को अब 2 + 4 या 2 * 4 या 2-4 या आदि करने के लिए असाइन कर सकते हैं
Skyler

यह व्यावहारिक रूप से सिर्फ करने के बराबर है str(eval(...)), इसलिए यह निश्चित रूप से सुरक्षित नहीं है eval
काया ३

निष्पादन /
निष्कासन के

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] ('गूंज थ्रू'एस.सप्लीट ())", {' बिलिंस ': कोई नहीं): # अपने सैंडबॉक्स
पर्किन्स

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})
बोर्न

8
यह सुरक्षित नहीं है । दुर्भावनापूर्ण कोड अभी भी निष्पादित किया जा सकता है।
फरमी विरोधाभास

This is not safe- अच्छा, मुझे लगता है कि यह पूरी तरह से बैश का उपयोग करने के रूप में सुरक्षित है। 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])
            )

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)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.