একটি স্ট্রিংয়ে গাণিতিক এক্সপ্রেশন মূল্যায়ন


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
হল এক্সওআর অপারেটর। প্রত্যাশিত মান 6.. আপনি সম্ভবত পাউ (২,৪) চান।
কেজিয়ান্নাকাকিস

25
বা আরও অযৌক্তিকভাবে 2 ** 4
ফরোর্টান

1
আপনি যদি eval ব্যবহার করতে না চান, তবে একমাত্র সমাধান হ'ল যথাযথ ব্যাকরণ পার্সার প্রয়োগ করা। পাইপার্সিংএকবার দেখুন
কেজিয়ান্নাকাকিস

উত্তর:


108

গাণিতিক এক্সপ্রেশন পার্স করতে পাইপার্সিং ব্যবহার করা যেতে পারে। বিশেষত, fourFn.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/smpleeval
ড্যানিয়েল ফেয়ারহেড

এর কাজকর্মের জন্য এটি কি বাড়ানো যেতে পারে import math?
হটসকে

2
নোট যে ast.parseনিরাপদ নয়। উদাহরণস্বরূপ ast.parse('()' * 1000000, '<string>', 'single')দোভাষীকে ক্র্যাশ করে।
আন্তি হাপালা

1
অ্যান্টিহাপাল @ এটি কি পাইথন ইন্টারপ্রেটারে বাগ আছে? যাইহোক, বড় ইনপুট তুচ্ছভাবে পরিচালনা করা হয় যেমন, ব্যবহার করে if len(expr) > 10000: raise ValueError
jfs

1
@ আন্টিহাপালা আপনি কি এমন উদাহরণ প্রদান করতে পারেন যা len(expr)চেক ব্যবহার করে স্থির করা যায় না ? অথবা আপনার বক্তব্যটি হল পাইথন বাস্তবায়নে বাগ রয়েছে এবং সেজন্য সাধারণভাবে নিরাপদ কোড লেখা অসম্ভব?
jfs

13

এর জন্য কিছু নিরাপদ বিকল্প eval()এবং * :sympy.sympify().evalf()

* SymPy sympifyএছাড়াও ডকুমেন্টেশন থেকে নিম্নোক্ত সতর্কতা অনিরাপদ অনুযায়ী।

সতর্কতা: নোট করুন যে এই ফাংশনটি ব্যবহার করে evalএবং তাই অযৌক্তিক ইনপুটটিতে ব্যবহার করা উচিত নয়।


10

ঠিক আছে, সুতরাং eval এর সাথে সমস্যাটি হ'ল এটি খুব সহজেই এর স্যান্ডবক্সটি এড়াতে পারে, এমনকি যদি আপনি পরিত্রাণ পানও __builtins__। স্যান্ডবক্স থেকে বেরিয়ে আসার সমস্ত পদ্ধতি ব্যবহারযোগ্য getattrবা object.__getattribute__( .অপারেটরের মাধ্যমে ) কিছু অনুমোদিত বস্তুর ( ''.__class__.__bases__[0].__subclasses__বা অনুরূপ) মাধ্যমে কোনও বিপজ্জনক বস্তুর রেফারেন্স পেতে নেমে আসে । getattrসেট __builtins__করে মুছে ফেলা হয় Noneobject.__getattribute__এটিই মুশকিল, যেহেতু এটি সরানো যায় না, উভয়ই objectস্থাবর এবং কারণ এটি অপসারণ করলে সমস্ত কিছু ভেঙে যায়। তবে __getattribute__কেবল .অপারেটরের মাধ্যমেই অ্যাক্সেসযোগ্য , সুতরাং আপনার ইনপুট থেকে পরিষ্কার করা যথেষ্ট যে এভাল নিশ্চিত করতে যথেষ্ট হয় যে এটি এর স্যান্ডবক্স থেকে বাঁচতে পারে না।
সূত্রগুলি প্রক্রিয়াকরণে, দশমিকের একমাত্র বৈধ ব্যবহার হ'ল এটি যখন পূর্ববর্তী বা অনুসরণ করা হয়[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-স্ট্রিং, যা নিম্নলিখিত হামলা অনুমতি দেয়: f"{eval('()' + chr(46) + '__class__')}"। কালো তালিকাভুক্তকরণের চেয়ে শ্বেত তালিকাভুক্তের ভিত্তিতে একটি সমাধান নিরাপদ হবে, তবে evalএ সমস্যাটি সমাধান না করেই এটি সমাধান করা আরও ভাল ।
কেয়া 3

ভবিষ্যতের ভাষার বৈশিষ্ট্যগুলি নতুন সুরক্ষা সংক্রান্ত সমস্যাগুলি প্রবর্তন করার জন্য এটি একটি দুর্দান্ত বিষয়।
পার্কিনস

8

আপনি অ্যাস্ট মডিউলটি ব্যবহার করতে পারেন এবং একটি নোডভিসিটার লিখতে পারেন যা যাচাই করে যে প্রতিটি নোডের প্রকারটি একটি শ্বেতলিস্টের অংশ।

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)বা অনুরূপ। কীটি কেবলমাত্র ওপকডগুলি নিরাপদ (লোডিং / স্টোরেজ মান, গণিত ক্রিয়াকলাপ, প্রত্যাবর্তন মান) এবং অনিরাপদ নয় (অ্যাট্রিবিউট লুকআপ) প্রয়োগ করে implement যদি আপনি চান যে ব্যবহারকারী ফাংশনগুলিতে কল করতে সক্ষম হবেন (উপরের শর্টকাটটি ব্যবহার না করার পুরো কারণ), সহজভাবে আপনার কার্যকরকরণটিকে 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(), তবে প্রথমে বিষয়টি নিশ্চিত করেছিলাম যে স্ট্রিংটি কোনও বৈধ গাণিতিক প্রকাশ, দূষিত কিছুর বিপরীতে। আপনি বৈধতার জন্য একটি রেজেক্স ব্যবহার করতে পারেন।

eval() এছাড়াও অতিরিক্ত যুক্তি গ্রহণ করে যা আপনি বৃহত্তর সুরক্ষার জন্য এটি চালিত নামস্থান সীমাবদ্ধ করতে ব্যবহার করতে পারেন।


3
তবে অবশ্যই নির্বিচার গাণিতিক অভিব্যক্তিগুলি যাচাই করতে নিয়মিত অভিব্যক্তির উপর নির্ভর করবেন না।
হাই পারফরম্যান্স মার্ক

@ হাই-পারফরম্যান্স মার্ক: হ্যাঁ, আমি অনুমান করি যে এটি কী ধরণের গাণিতিক অভিব্যক্তিগুলি মনে রাখে তার উপর নির্ভর করে। । । যেমন, সংখ্যার শুধু সহজ গাণিতিক এবং +, -, *, /, **, (, )বা কিছু বেশি জটিল
টিম গুডম্যান

@ টিম - এটি () আমি উদ্বিগ্ন, বা বরং ((((())))))) সত্যিকার অর্থে, আমি মনে করি ওপিকে তাদের সম্পর্কে চিন্তা করা উচিত, আমার ব্রাউজ ওপির সমস্যার দ্বারা উদ্দীপ্ত।
হাই পারফরম্যান্স মার্ক

2
eval()আপনি যদি নেপস সীমাবদ্ধ করে এমনকি ইনপুট নিয়ন্ত্রণ না করেন তবে ব্যবহার করবেন না যেমন eval("9**9**9**9**9**9**9**9", {'__builtins__': None})সিপিইউ, মেমরি গ্রহণ করে।
jfs

3
ইওল এর নামস্থান সীমাবদ্ধ করা সুরক্ষা যোগ করে না
আন্তি হাপালা

5

এটি একটি ব্যাপক দেরী উত্তর, তবে আমি ভবিষ্যতের রেফারেন্সের জন্য দরকারী বলে মনে করি। আপনার নিজের ম্যাথ পার্সার লেখার পরিবর্তে (যদিও উপরে পাইপার্সিংয়ের উদাহরণটি দুর্দান্ত) আপনি সিমপি ব্যবহার করতে পারেন। আমার এটির সাথে প্রচুর অভিজ্ঞতা নেই তবে এটিতে যে কোনও নির্দিষ্ট প্রয়োগের জন্য লেখার সম্ভাবনা রয়েছে তার চেয়ে অনেক বেশি শক্তিশালী গণিত ইঞ্জিন রয়েছে এবং বেসিক এক্সপ্রেশন মূল্যায়ন খুব সহজ:

>>> 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 *আরও অনেকগুলি ফাংশন সমর্থন নিয়ে আসে, যেমন ট্রিগ ফাংশন, বিশেষ ফাংশন ইত্যাদি but তবে কোথা থেকে কী আসছে তা দেখানোর জন্য আমি এখানে এড়াতে পেরেছি।


3
সিম্পি কি "নিরাপদ"? এমন অনেকগুলি পোস্ট রয়েছে বলে মনে হয় যে এটি ইওল () এর চারপাশে একটি মোড়ক যা একইভাবে কাজে লাগানো যেতে পারে। এছাড়াও evalfন্যালি ndarrays নিতে হবে না।
মার্ক মিকোফস্কি

14
অবিশ্বস্ত ইনপুট জন্য কোনও সিম্পি নিরাপদ নয়। sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")এই কলগুলির পরিবর্তে subprocess.Popen()আমি যা করেছি তার চেষ্টা করুন । অন্যান্য কম্পিউটারে সূচক সম্ভবত ভিন্ন হবে। এটি নেড ব্যাচেল্ডার শোষণের একটি বৈকল্পিকlsrm -rf /
মার্ক মিকোফস্কি

1
প্রকৃতপক্ষে, এটি মোটেও সুরক্ষা যোগ করে না।
অ্যান্টি হাপাল

4

[আমি জানি এটি একটি পুরানো প্রশ্ন, তবে তারা কার্যকর হওয়ার সাথে সাথে নতুন কার্যকর সমাধানগুলি দেখানো ভাল]

পাইথন .6.। থেকে, এই ক্ষমতাটি এখন "এফ-স্ট্রিংস" বানানো ভাষায় তৈরি করা হয়েছে

দেখুন: পিইপি 498 - আক্ষরিক স্ট্রিং ইন্টারপোলেশন

উদাহরণস্বরূপ ( fউপসর্গটি নোট করুন ):

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

7
খুব আকর্ষণীয় লিঙ্ক। তবে আমি অনুমান করি যে এফ-স্ট্রিংগুলি লেখার উত্স কোডটি সহজ করার জন্য এখানে রয়েছে, যখন প্রশ্নটি ভেরিয়েবলের ভিতরে স্ট্রিং (সম্ভবত অবিশ্বস্ত উত্স থেকে) নিয়ে কাজ করার বিষয়ে মনে হয়। সেক্ষেত্রে এফ-স্ট্রিং ব্যবহার করা যাবে না।
বার্নহার্ড

f '{2 {অপারেটর} 4}' এর প্রভাবের জন্য কিছু করার কোনও উপায় আছে যেখানে আপনি এখন অপারেটরকে 2 + 4 বা 2 * 4 বা 2-4 বা অন্যান্য করতে
সুনির্দিষ্ট করতে পারেন

এটি ব্যবহারিকভাবে কেবল করার সমতুল্য str(eval(...)), তাই এটি অবশ্যই এর চেয়ে নিরাপদ নয় eval
কেয়া 3


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] ( 'প্রতিধ্বনি পেয়েছিলাম through'.split ())", {' builtins ': কোনটি}) আপনার স্যান্ডবক্স #escapes
পার্কিনস

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- ভাল, আমি মনে করি এটি সামগ্রিকভাবে ব্যাশ ব্যবহারের মতোই নিরাপদ। বিটিডাব্লু: eval('math.sqrt(2.0)')<- "গণিত" " উপরে লেখা হিসাবে প্রয়োজন।
হান্নু

0

এখানে eval ব্যবহার না করেই আমার সমস্যার সমাধান। পাইথন 2 এবং পাইথন 3 নিয়ে কাজ করে। এটি নেতিবাচক সংখ্যা নিয়ে কাজ করে না।

$ 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.