Mengevaluasi ekspresi matematika dalam string

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

Ini mengembalikan kesalahan berikut:

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

Saya tahu itu evalbisa mengatasi ini, tetapi bukankah ada metode yang lebih baik dan - yang lebih penting - lebih aman untuk mengevaluasi ekspresi matematika yang disimpan dalam string?

Pieter
sumber
6
^ adalah operator XOR. Nilai yang diharapkan adalah 6. Anda mungkin menginginkan pow (2,4).
kgiannakakis
25
atau lebih pythonically 2 ** 4
fortran
1
Jika Anda tidak ingin menggunakan eval, satu-satunya solusi adalah menerapkan pengurai tata bahasa yang sesuai. Lihat pyparsing .
kgiannakakis

Jawaban:

108

Pyparsing dapat digunakan untuk mengurai ekspresi matematika. Secara khusus, fourFn.py menunjukkan bagaimana mengurai ekspresi aritmatika dasar. Di bawah, saya telah membungkus ulang fourFn menjadi kelas parser numerik agar lebih mudah digunakan kembali.

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

Anda bisa menggunakannya seperti ini

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

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

eval itu jahat

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

Catatan: bahkan jika Anda menggunakan set __builtins__untuk Noneitu masih mungkin untuk keluar menggunakan introspeksi:

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

Evaluasi ekspresi aritmatika menggunakan 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)

Anda dapat dengan mudah membatasi rentang yang diizinkan untuk setiap operasi atau hasil antara, misalnya, untuk membatasi argumen input untuk 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

Atau untuk membatasi besaran hasil antara:

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

Contoh

>>> 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:
jfs
sumber
29
Posting yang sangat keren, Terima kasih. Saya telah mengambil konsep itu, dan mencoba membuat perpustakaan yang seharusnya mudah digunakan: github.com/danthedeckie/simpleeval
Daniel Fairhead
dapatkah ini diperpanjang untuk fungsi import math?
Hotschke
2
Perhatikan itu ast.parsetidak aman. Misalnya ast.parse('()' * 1000000, '<string>', 'single')crash interpreter.
Antti Haapala
1
@AnttiHaapala contoh yang bagus. Apakah ini bug dalam interpreter Python? Bagaimanapun, input besar ditangani dengan mudah misalnya, menggunakan if len(expr) > 10000: raise ValueError.
jfs
1
@AnttiHaapala dapatkah Anda memberikan contoh yang tidak dapat diperbaiki menggunakan len(expr)cek? Atau maksud Anda adalah ada bug dalam implementasi Python dan oleh karena itu tidak mungkin untuk menulis kode aman secara umum?
jfs
13

Beberapa alternatif yang lebih aman untuk eval()dan * :sympy.sympify().evalf()

* SymPy sympifyjuga tidak aman menurut peringatan berikut dari dokumentasi.

Peringatan: Perhatikan bahwa fungsi ini menggunakan eval, dan karenanya tidak boleh digunakan pada input yang tidak dibersihkan.

Mark Mikofski
sumber
10

Oke, jadi masalah dengan eval adalah ia dapat keluar dari kotak pasirnya terlalu mudah, bahkan jika Anda membuangnya __builtins__. Semua metode untuk melarikan diri dari kotak pasir turun menggunakan getattratau object.__getattribute__(melalui .operator) untuk mendapatkan referensi ke beberapa objek berbahaya melalui beberapa objek yang diizinkan ( ''.__class__.__bases__[0].__subclasses__atau serupa). getattrdihilangkan dengan menyetel __builtins__ke None. object.__getattribute__adalah yang sulit, karena tidak bisa begitu saja dihilangkan, baik karena objecttidak dapat diubah dan karena menghapusnya akan merusak segalanya. Namun, __getattribute__hanya dapat diakses melalui .operator, jadi membersihkannya dari masukan Anda sudah cukup untuk memastikan eval tidak dapat keluar dari kotak pasirnya.
Dalam memproses rumus, satu-satunya penggunaan desimal yang valid adalah ketika diawali atau diikuti oleh[0-9], jadi kami hanya menghapus semua contoh lainnya dari ..

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

Perhatikan bahwa sementara python biasanya diperlakukan 1 + 1.sebagai 1 + 1.0, ini akan menghapus .jejak dan meninggalkan Anda 1 + 1. Anda dapat menambahkan ),, dan EOFke daftar hal-hal yang boleh diikuti ., tetapi mengapa repot-repot?

Perkins
sumber
Pertanyaan terkait dengan pembahasan menarik dapat ditemukan di sini .
djvg
3
Apakah argumen tentang penghapusan .benar atau tidak pada saat ini, ini meninggalkan potensi kerentanan keamanan jika versi Python di masa mendatang memperkenalkan sintaks baru yang memungkinkan objek atau fungsi yang tidak aman diakses dengan cara lain. Solusi ini sudah tidak aman di Python 3.6 karena f-string, yang memungkinkan serangan berikut: f"{eval('()' + chr(46) + '__class__')}". Solusi berdasarkan daftar putih daripada daftar hitam akan lebih aman, tetapi sebenarnya lebih baik menyelesaikan masalah ini tanpa masalah evalsama sekali.
kaya3
Itu poin bagus tentang fitur bahasa masa depan yang memperkenalkan masalah keamanan baru.
Perkins
8

Anda dapat menggunakan modul ast dan menulis NodeVisitor yang memverifikasi bahwa jenis setiap node adalah bagian dari daftar putih.

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)

Karena berfungsi melalui daftar putih daripada daftar hitam, ini aman. Satu-satunya fungsi dan variabel yang dapat diaksesnya adalah yang Anda berikan akses secara eksplisit. Saya mengisi dikt dengan fungsi yang berhubungan dengan matematika sehingga Anda dapat dengan mudah memberikan akses ke fungsi tersebut jika Anda mau, tetapi Anda harus menggunakannya secara eksplisit.

Jika string mencoba memanggil fungsi yang belum disediakan, atau memanggil metode apa pun, pengecualian akan dimunculkan, dan tidak akan dijalankan.

Karena ini menggunakan parser dan evaluator bawaan Python, ini juga mewarisi aturan promosi dan prioritas Python juga.

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

Kode di atas hanya diuji pada Python 3.

Jika diinginkan, Anda dapat menambahkan dekorator batas waktu pada fungsi ini.

Kevin
sumber
7

Alasan evaldan execsangat berbahaya adalah bahwa compilefungsi default akan menghasilkan bytecode untuk ekspresi python yang valid, dan default evalatau execakan menjalankan bytecode python yang valid. Semua jawaban sampai saat ini berfokus pada pembatasan bytecode yang dapat dihasilkan (dengan membersihkan masukan) atau membangun bahasa khusus domain Anda sendiri menggunakan AST.

Sebaliknya, Anda dapat dengan mudah membuat evalfungsi sederhana yang tidak mampu melakukan hal jahat dan dapat dengan mudah melakukan pemeriksaan waktu proses pada memori atau waktu yang digunakan. Tentunya jika matematika itu sederhana, maka ada jalan pintas.

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]

Cara kerjanya sederhana, ekspresi matematika konstan apa pun dievaluasi dengan aman selama kompilasi dan disimpan sebagai konstanta. Objek kode yang dikembalikan oleh kompilasi terdiri dari d, yang merupakan bytecode untuk LOAD_CONST, diikuti oleh jumlah konstanta yang akan dimuat (biasanya yang terakhir dalam daftar), diikuti oleh S, yang merupakan bytecode untuk RETURN_VALUE. Jika pintasan ini tidak berfungsi, berarti input pengguna bukanlah ekspresi konstan (berisi panggilan variabel atau fungsi atau serupa).

Ini juga membuka pintu ke beberapa format input yang lebih canggih. Sebagai contoh:

stringExp = "1 + cos(2)"

Ini membutuhkan evaluasi bytecode, yang masih cukup sederhana. Bytecode Python adalah bahasa berorientasi tumpukan, jadi semuanya adalah masalah sederhana TOS=stack.pop(); op(TOS); stack.put(TOS)atau serupa. Kuncinya adalah hanya mengimplementasikan opcode yang aman (memuat / menyimpan nilai, operasi matematika, mengembalikan nilai) dan bukan yang tidak aman (pencarian atribut). Jika Anda ingin pengguna dapat memanggil fungsi (seluruh alasan untuk tidak menggunakan pintasan di atas), buat implementasi Anda CALL_FUNCTIONhanya mengizinkan fungsi dalam daftar 'aman'.

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

Jelas, versi sebenarnya dari ini akan sedikit lebih lama (ada 119 opcode, 24 di antaranya terkait dengan matematika). Menambahkan STORE_FASTdan beberapa lainnya akan memungkinkan untuk memasukkan seperti 'x=5;return x+xatau serupa, dengan mudah. Ia bahkan dapat digunakan untuk menjalankan fungsi yang dibuat pengguna, selama fungsi yang dibuat oleh pengguna dijalankan sendiri melalui VMeval (jangan membuatnya dapat dipanggil !!! atau dapat digunakan sebagai panggilan balik di suatu tempat). Menangani loop membutuhkan dukungan untuk gotobytecode, yang berarti mengubah dari foriterator menjadi yang paling jelas).while dan mempertahankan pointer ke instruksi saat ini, tetapi tidak terlalu sulit. Untuk resistansi terhadap DOS, loop utama harus memeriksa berapa lama waktu telah berlalu sejak dimulainya kalkulasi, dan operator tertentu harus menolak input melebihi batas yang wajar (BINARY_POWER

Meskipun pendekatan ini agak lebih panjang dari parser tata bahasa sederhana untuk ekspresi sederhana (lihat di atas tentang hanya mengambil konstanta yang dikompilasi), pendekatan ini meluas dengan mudah ke masukan yang lebih rumit, dan tidak memerlukan penanganan tata bahasa ( compileambil sesuatu yang rumit secara sewenang-wenang dan menguranginya menjadi urutan instruksi sederhana).

Perkins
sumber
6

Saya pikir saya akan menggunakan eval(), tetapi pertama-tama akan memeriksa untuk memastikan string tersebut adalah ekspresi matematika yang valid, sebagai lawan dari sesuatu yang berbahaya. Anda bisa menggunakan regex untuk validasi.

eval() juga membutuhkan argumen tambahan yang dapat Anda gunakan untuk membatasi namespace yang dioperasikannya untuk keamanan yang lebih baik.

Tim Goodman
sumber
3
Namun, tentu saja, jangan mengandalkan ekspresi reguler untuk memvalidasi ekspresi matematika arbitrer.
Kinerja Tinggi Mark
@ High-Performance Mark: Ya, saya rasa itu tergantung pada ekspresi matematika seperti apa yang dia pikirkan. . . misalnya, hanya aritmatika sederhana dengan angka dan +, -, *, /, **, (, )atau sesuatu yang lebih rumit
Tim Goodman
@ Tim - itu () yang saya khawatirkan, atau lebih tepatnya (((((())))))). Sebenarnya, saya pikir OP harus mengkhawatirkan mereka, alis saya tidak berkerut karena masalah OP.
Kinerja Tinggi Mark
2
Jangan gunakan eval()jika Anda tidak mengontrol input bahkan jika Anda membatasi namespace misalnya, eval("9**9**9**9**9**9**9**9", {'__builtins__': None})menggunakan CPU, memori.
jfs
3
Membatasi namespace eval tidak menambah keamanan .
Antti Haapala
5

Ini adalah balasan yang sangat terlambat, tapi menurut saya berguna untuk referensi di masa mendatang. Daripada menulis parser matematika Anda sendiri (meskipun contoh penguraian di atas bagus), Anda dapat menggunakan SymPy. Saya tidak memiliki banyak pengalaman dengannya, tetapi ini berisi mesin matematika yang jauh lebih kuat daripada yang mungkin ditulis siapa pun untuk aplikasi tertentu dan evaluasi ekspresi dasar sangat mudah:

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

Sangat keren! A from sympy import *membawa lebih banyak dukungan fungsi, seperti fungsi trigonometri, fungsi khusus, dll., Tetapi saya menghindari itu di sini untuk menunjukkan apa yang datang dari mana.

andybuckley.dll
sumber
3
Apakah sympy "aman"? Tampaknya ada banyak posting yang menyarankan itu adalah pembungkus eval () yang dapat dieksploitasi dengan cara yang sama. Juga evalftidak mengambil ndarrays numpy.
Mark Mikofski
14
Tidak ada sympy tidak aman untuk input tidak tepercaya. Coba sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")panggilan ini subprocess.Popen()yang saya lulus lssebagai gantinya rm -rf /. Indeks mungkin akan berbeda di komputer lain. Ini adalah varian dari exploit Ned Batchelder
Mark Mikofski
1
Memang tidak menambah keamanan sama sekali.
Antti Haapala
4

[Saya tahu ini adalah pertanyaan lama, tetapi ada baiknya menunjukkan solusi baru yang berguna saat muncul]

Sejak python3.6, kemampuan ini sekarang dibangun ke dalam bahasa , yang disebut "f-string" .

Lihat: PEP 498 - Interpolasi String Literal

Misalnya (perhatikan fawalannya):

f'{2**4}'
=> '16'
shx2
sumber
7
Tautan yang sangat menarik. Tapi saya kira f-string ada di sini untuk membuat penulisan kode sumber lebih mudah, sementara pertanyaannya tampaknya tentang bekerja dengan string di dalam variabel (mungkin dari sumber yang tidak tepercaya). f-string tidak dapat digunakan dalam kasus itu.
Bernhard
apakah ada cara untuk melakukan sesuatu yang mempengaruhi f '{2 {operator} 4}' di mana Anda sekarang dapat menetapkan operator untuk melakukan 2 + 4 atau 2 * 4 atau 2-4 atau dll
Skyler
Ini secara praktis setara dengan hanya melakukan str(eval(...)) , jadi tentu saja tidak lebih aman dari eval.
kaya3
Tampaknya sama dengan exec / eval ...
Victor VosMottor berterima kasih kepada Monica
0

Gunakan evaldi namespace yang bersih:

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

Namespace yang bersih harus mencegah injeksi. Misalnya:

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

Jika tidak, Anda akan mendapatkan:

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

Anda mungkin ingin memberikan akses ke modul matematika:

>>> import math
>>> ns = vars(math).copy()
>>> ns['__builtins__'] = None
>>> eval('cos(pi/3)', ns)
0.50000000000000011
krawyoti
sumber
35
eval ("(1) .__ class __.__ bases __ [0] .__ subclass __ () [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})mengeksekusi shell bourne ...
Antti Haapala
8
Ini tidak aman . Kode berbahaya masih bisa dijalankan.
Fermi paradox
This is not safe- yah, saya rasa ini sama amannya dengan menggunakan bash secara keseluruhan. BTW:eval('math.sqrt(2.0)') <- "matematika." diperlukan seperti yang tertulis di atas.
Hannu
0

Inilah solusi saya untuk masalah tanpa menggunakan eval. Bekerja dengan Python2 dan Python3. Ini tidak bekerja dengan angka negatif.

$ 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)
GALERI KESENIAN
sumber