Auswerten eines mathematischen Ausdrucks in einer Zeichenfolge


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

Dies gibt den folgenden Fehler zurück:

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

Ich weiß, dass evaldies umgangen werden kann, aber gibt es nicht eine bessere und - was noch wichtiger ist - sicherere Methode, um einen mathematischen Ausdruck zu bewerten, der in einer Zeichenfolge gespeichert ist?


6
^ ist der XOR-Operator. Der erwartete Wert ist 6. Sie möchten wahrscheinlich pow (2,4).
kgiannakakis

25
oder mehr pythonisch 2 ** 4
fortran

1
Wenn Sie eval nicht verwenden möchten, besteht die einzige Lösung darin, den entsprechenden Grammatik-Parser zu implementieren. Schauen Sie sich Pyparsing an .
kgiannakakis

Antworten:


108

Pyparsing kann verwendet werden, um mathematische Ausdrücke zu analysieren. Insbesondere zeigt fourFn.py , wie grundlegende arithmetische Ausdrücke analysiert werden. Unten habe ich fourFn zur einfacheren Wiederverwendung in eine numerische Parser-Klasse umwickelt.

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

Sie können es so verwenden

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

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

180

eval ist böse

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

Hinweis: Selbst wenn Sie set __builtins__to Noneit verwenden , kann es dennoch möglich sein, mit Introspektion auszubrechen:

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

Bewerten Sie den arithmetischen Ausdruck mit 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)

Sie können den zulässigen Bereich für jede Operation oder jedes Zwischenergebnis leicht einschränken, z. B. um Eingabeargumente für Folgendes einzuschränken 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

Oder um die Größe der Zwischenergebnisse zu begrenzen:

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

Beispiel

>>> 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
Sehr cooler Beitrag, danke. Ich habe dieses Konzept übernommen und versucht, eine Bibliothek zu erstellen, die einfach zu verwenden sein sollte: github.com/danthedeckie/simpleeval
Daniel Fairhead

kann dies für Funktionen von erweitert werden import math?
Hotschke

2
Beachten Sie, dass dies ast.parsenicht sicher ist. Zum Beispiel ast.parse('()' * 1000000, '<string>', 'single')stürzt der Interpreter ab.
Antti Haapala

1
@AnttiHaapala gutes Beispiel. Ist es ein Fehler im Python-Interpreter? Wie auch immer, große Eingaben werden trivial behandelt, z if len(expr) > 10000: raise ValueError. B. mit .
JFS

1
@AnttiHaapala Könnten Sie ein Beispiel angeben, das mit der len(expr)Prüfung nicht behoben werden kann ? Oder ist Ihr Punkt, dass es Fehler in der Python-Implementierung gibt und es daher unmöglich ist, generell sicheren Code zu schreiben?
JFS

13

Einige sicherere Alternativen zu eval()und * :sympy.sympify().evalf()

* SymPy sympifyist gemäß der folgenden Warnung aus der Dokumentation ebenfalls unsicher.

Warnung: Beachten Sie, dass diese Funktion verwendet evalwird und daher nicht für nicht bereinigte Eingaben verwendet werden sollte.


10

Okay, das Problem mit eval ist, dass es zu leicht aus seiner Sandbox entkommen kann, selbst wenn Sie es loswerden __builtins__. Alle Methoden zum Entkommen aus der Sandbox bestehen darin, getattroder object.__getattribute__(über den .Bediener) zu verwenden, um über ein zulässiges Objekt ( ''.__class__.__bases__[0].__subclasses__oder ähnliches) einen Verweis auf ein gefährliches Objekt zu erhalten . getattrwird durch Einstellen __builtins__auf beseitigt None. object.__getattribute__ist das Schwierige, da es nicht einfach entfernt werden kann, sowohl weil objectes unveränderlich ist als auch weil das Entfernen alles kaputt machen würde. Der Zugriff __getattribute__ist jedoch nur über den .Bediener möglich, sodass das Löschen Ihrer Eingabe ausreicht, um sicherzustellen, dass eval nicht aus seiner Sandbox entweichen kann.
In Verarbeitungsformeln ist die einzige gültige Verwendung einer Dezimalstelle, wenn ihr vorangestellt oder gefolgt wird[0-9], also entfernen wir einfach alle anderen Instanzen von ..

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

Beachten Sie, dass Python normalerweise so behandelt 1 + 1.wie 1 + 1.0, dies jedoch das Nachlaufen entfernt .und Sie mit belässt 1 + 1. Man könnte hinzufügen ), und EOFauf die Liste der Dinge zu folgen erlaubt ., aber warum die Mühe?


Eine verwandte Frage mit interessanter Diskussion finden Sie hier .
DJVG

3
Unabhängig davon, ob das Argument zum Entfernen .im Moment richtig ist oder nicht, besteht das Potenzial für Sicherheitslücken, wenn zukünftige Versionen von Python eine neue Syntax einführen, mit der auf unsichere Objekte oder Funktionen auf andere Weise zugegriffen werden kann. Diese Lösung ist in Python 3.6 aufgrund von F-Strings, die den folgenden Angriff ermöglichen, bereits unsicher : f"{eval('()' + chr(46) + '__class__')}". Eine Lösung, die eher auf Whitelisting als auf Blacklisting basiert, ist sicherer, aber es ist wirklich besser, dieses Problem ohne zu lösen eval.
kaya3

Dies ist ein hervorragender Punkt für zukünftige Sprachfunktionen, die neue Sicherheitsprobleme einführen.
Perkins

8

Sie können das ast-Modul verwenden und einen NodeVisitor schreiben, der überprüft, ob der Typ jedes Knotens Teil einer Whitelist ist.

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)

Da es über eine Whitelist und nicht über eine Blacklist funktioniert, ist es sicher. Die einzigen Funktionen und Variablen, auf die zugegriffen werden kann, sind diejenigen, auf die Sie explizit zugreifen. Ich habe ein Diktat mit mathematischen Funktionen gefüllt, damit Sie bei Bedarf problemlos auf diese zugreifen können, aber Sie müssen es explizit verwenden.

Wenn die Zeichenfolge versucht, nicht bereitgestellte Funktionen aufzurufen oder Methoden aufzurufen, wird eine Ausnahme ausgelöst und nicht ausgeführt.

Da hierfür der in Python integrierte Parser und Evaluator verwendet wird, werden auch die Prioritäts- und Heraufstufungsregeln von Python übernommen.

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

Der obige Code wurde nur unter Python 3 getestet.

Falls gewünscht, können Sie dieser Funktion einen Timeout-Dekorator hinzufügen.


7

Der Grund evalund execdie Gefahr besteht darin, dass die Standardfunktion compileBytecode für jeden gültigen Python-Ausdruck generiert, und die Standardfunktion evaloderexec einen gültigen Python-Bytecode ausführt ausführt. Alle bisherigen Antworten konzentrierten sich auf die Einschränkung des Bytecodes, der generiert werden kann (durch Bereinigen der Eingabe) oder das Erstellen einer eigenen domänenspezifischen Sprache mithilfe des AST.

Stattdessen können Sie auf einfache Weise eine einfache evalFunktion erstellen , die nicht in der Lage ist, etwas Schändliches zu tun, und auf einfache Weise Laufzeitprüfungen des Speichers oder der verwendeten Zeit durchführen lassen. Wenn es sich um einfache Mathematik handelt, gibt es natürlich eine Abkürzung.

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]

Die Funktionsweise ist einfach: Jeder konstante mathematische Ausdruck wird während der Kompilierung sicher ausgewertet und als Konstante gespeichert. Das von compile zurückgegebene Codeobjekt besteht aus ddem Bytecode für LOAD_CONST, gefolgt von der Nummer der zu ladenden Konstante (normalerweise die letzte in der Liste), gefolgt vonS dem Bytecode für RETURN_VALUE. Wenn diese Verknüpfung nicht funktioniert, bedeutet dies, dass die Benutzereingabe kein konstanter Ausdruck ist (enthält eine Variable oder einen Funktionsaufruf oder ähnliches).

Dies öffnet auch die Tür zu einigen komplexeren Eingabeformaten. Beispielsweise:

stringExp = "1 + cos(2)"

Dies erfordert die tatsächliche Auswertung des Bytecodes, was immer noch recht einfach ist. Python-Bytecode ist eine stapelorientierte Sprache, daher ist alles einfach TOS=stack.pop(); op(TOS); stack.put(TOS)oder ähnlich. Der Schlüssel besteht darin, nur die Opcodes zu implementieren, die sicher sind (Laden / Speichern von Werten, mathematische Operationen, Zurückgeben von Werten) und nicht unsichere (Attributsuche). Wenn Sie möchten, dass der Benutzer Funktionen aufrufen kann (der ganze Grund, die obige Verknüpfung nicht zu verwenden), können Sie einfach CALL_FUNCTIONnur zulässige Funktionen in einer "sicheren" Liste implementieren .

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

Offensichtlich wäre die eigentliche Version etwas länger (es gibt 119 Opcodes, von denen 24 mathematisch sind). Das Hinzufügen STORE_FASTund ein paar andere würden eine Eingabe wie 'x=5;return x+xoder ähnlich ermöglichen, trivial einfach. Es kann sogar verwendet werden, um vom Benutzer erstellte Funktionen auszuführen, solange die vom Benutzer erstellten Funktionen selbst über VMeval ausgeführt werden (machen Sie sie nicht aufrufbar !!! oder sie könnten irgendwo als Rückruf verwendet werden). Die Behandlung von Schleifen erfordert die Unterstützung der gotoBytecodes, was bedeutet, dass der Wechsel von einem Wesen am offensichtlichsten ist.for Iterator zu whileeinem Zeiger auf den aktuellen Befehl und dieser beibehalten wird, dies ist jedoch nicht zu schwierig. Für den Widerstand gegen DOS sollte die Hauptschleife prüfen, wie viel Zeit seit Beginn der Berechnung vergangen ist, und bestimmte Operatoren sollten die Eingabe über einen angemessenen Grenzwert verweigern (BINARY_POWER

Dieser Ansatz ist zwar etwas länger als ein einfacher Grammatik-Parser für einfache Ausdrücke (siehe oben, in dem nur die kompilierte Konstante erfasst wird), erstreckt sich jedoch leicht auf kompliziertere Eingaben und erfordert keine Behandlung der Grammatik ( compilenehmen Sie etwas willkürlich Kompliziertes und reduzieren Sie es auf eine Folge einfacher Anweisungen).


6

Ich denke, ich würde verwenden eval(), würde aber zuerst überprüfen, ob die Zeichenfolge ein gültiger mathematischer Ausdruck ist, im Gegensatz zu etwas Bösartigem. Sie können einen regulären Ausdruck für die Validierung verwenden.

eval() Außerdem werden zusätzliche Argumente verwendet, mit denen Sie den Namespace, in dem es ausgeführt wird, für mehr Sicherheit einschränken können.


3
Aber verlassen Sie sich natürlich nicht auf reguläre Ausdrücke, um beliebige mathematische Ausdrücke zu validieren.
High Performance Mark

@ High-Performance Mark: Ja, ich denke, es hängt davon ab, welche Art von mathematischen Ausdrücken er im Sinn hat. . . zB nur einfache Arithmetik mit Zahlen und +, -, *, /, **, (, )oder etwas kompliziert
Tim Goodman

@ Tim - es ist das (), um das ich mir Sorgen mache, oder eher das (((((()))))). In Wahrheit denke ich, dass OP sich um sie sorgen sollte, meine Stirn ist von den Problemen von OP nicht gefurcht.
High Performance Mark

2
Nicht verwenden, eval()wenn Sie die Eingabe nicht steuern, auch wenn Sie den Namespace einschränken, z. B. eval("9**9**9**9**9**9**9**9", {'__builtins__': None})CPU und Speicher verbrauchen.
JFS

3
Das Einschränken des Namespace von eval erhöht die Sicherheit nicht .
Antti Haapala

5

Dies ist eine massiv verspätete Antwort, aber ich halte sie für nützlich, um später darauf zurückgreifen zu können. Anstatt Ihren eigenen Mathe-Parser zu schreiben (obwohl das obige Pyparsing-Beispiel großartig ist), können Sie SymPy verwenden. Ich habe nicht viel Erfahrung damit, aber es enthält eine viel leistungsfähigere mathematische Engine, als irgendjemand wahrscheinlich für eine bestimmte Anwendung schreibt, und die grundlegende Auswertung von Ausdrücken ist sehr einfach:

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

Sehr cool! A from sympy import *bietet viel mehr Funktionsunterstützung, wie Triggerfunktionen, Sonderfunktionen usw., aber ich habe dies hier vermieden, um zu zeigen, was von wo kommt.


3
Ist Sympy "sicher"? Es scheint zahlreiche Posts zu geben , die darauf hindeuten, dass es sich um einen Wrapper um eval () handelt, der auf die gleiche Weise ausgenutzt werden könnte. evalfNimmt auch keine numpy ndarrays.
Mark Mikofski

14
Kein Sympy ist für nicht vertrauenswürdige Eingaben nicht sicher. Versuchen Sie sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")diese Anrufe, subprocess.Popen()die ich lsstatt übergeben habe rm -rf /. Der Index wird wahrscheinlich auf anderen Computern anders sein. Dies ist eine Variante des Ned Batchelder Exploits
Mark Mikofski

1
In der Tat trägt es überhaupt nicht zur Sicherheit bei.
Antti Haapala

4

[Ich weiß, dass dies eine alte Frage ist, aber es lohnt sich, auf neue nützliche Lösungen hinzuweisen, wenn sie auftauchen.]

Da python3.6 wird diese Fähigkeit nun in die Sprache integriert , prägt „f-Strings“ .

Siehe: PEP 498 - Literal String Interpolation

Zum Beispiel (beachten Sie das fPräfix):

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

7
Sehr interessanter Link. Aber ich denke, F-Strings sollen das Schreiben von Quellcode erleichtern, während die Frage anscheinend darin besteht, mit Strings innerhalb von Variablen (möglicherweise aus nicht vertrauenswürdigen Quellen) zu arbeiten. f-Strings können in diesem Fall nicht verwendet werden.
Bernhard

Gibt es eine Möglichkeit, etwas mit der Wirkung von f '{2 {operator} 4}' zu tun, wo Sie dem Operator jetzt 2 + 4 oder 2 * 4 oder 2-4 oder usw. zuweisen können
Skyler

Dies ist praktisch gleichbedeutend mit einfachem Tun str(eval(...)), daher ist es sicherlich nicht sicherer als eval.
Kaya3

Scheint mit exec / eval dasselbe zu sein ...
Victor VosMottor bedankt sich bei Monica

0

Verwendung evalin einem sauberen Namespace:

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

Der saubere Namespace sollte die Injektion verhindern. Zum Beispiel:

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

Sonst würden Sie bekommen:

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

Möglicherweise möchten Sie Zugriff auf das Mathematikmodul gewähren:

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

35
eval ("(1) .__ Klasse __.__ Basen __ [0] .__ Unterklassen __ () [81] ('echo got through'.split ())", {' builtins ': None}) #escapes your sandbox
Perkins

6
Python 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})führt die Bourne-Shell aus ...
Antti Haapala

8
Das ist nicht sicher . Schädlicher Code kann weiterhin ausgeführt werden.
Fermi Paradox

This is not safe- Nun, ich denke, es ist genauso sicher wie die Verwendung von Bash insgesamt. Übrigens: eval('math.sqrt(2.0)')<- "Mathe". ist wie oben beschrieben erforderlich.
Hannu

0

Hier ist meine Lösung für das Problem ohne eval. Funktioniert mit Python2 und Python3. Es funktioniert nicht mit negativen Zahlen.

$ 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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.