Bir dizedeki matematiksel ifadeyi değerlendirme


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

Bu, aşağıdaki hatayı döndürür:

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

Bunun bu evalsorunu çözebileceğini biliyorum , ancak bir dizede saklanan matematiksel bir ifadeyi değerlendirmek için daha iyi ve daha da önemlisi daha güvenli bir yöntem yok mu?


6
^, XOR operatörüdür. Beklenen değer 6'dır. Muhtemelen pow (2,4) istiyorsunuz.
kgiannakakis

25
veya daha fazla pitonik olarak 2 ** 4
fortran

1
Eval kullanmak istemiyorsanız, tek çözüm uygun dilbilgisi ayrıştırıcısını uygulamaktır. Pyparsing'e bir göz atın .
kgiannakakis

Yanıtlar:


108

Pyparsing , matematiksel ifadeleri ayrıştırmak için kullanılabilir. Özellikle, fourFn.py temel aritmetik ifadelerin nasıl ayrıştırılacağını gösterir. Aşağıda, daha kolay yeniden kullanım için fourFn'yi sayısal ayrıştırıcı sınıfına yeniden sardım.

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

Bunu böyle kullanabilirsin

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

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

180

eval kötü

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

Not: set kullansanız bile __builtins__üzere Nonekendisine hala iç gözlem kullanılarak patlak mümkün olabilir:

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

Kullanarak aritmetik ifadeyi değerlendirin 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)

Her işlem veya herhangi bir ara sonuç için izin verilen aralığı kolayca sınırlayabilirsiniz, örneğin aşağıdakiler için giriş argümanlarını sınırlamak için 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

Veya ara sonuçların büyüklüğünü sınırlamak için:

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

Misal

>>> 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
Çok güzel gönderi, teşekkürler. Bu konsepti aldım ve kullanımı kolay bir kitaplık oluşturmaya çalıştım: github.com/danthedeckie/simpleeval
Daniel Fairhead

bu, işlevleri için genişletilebilir import mathmi?
Hotschke

2
ast.parseGüvenli olmadığını unutmayın . Örneğin ast.parse('()' * 1000000, '<string>', 'single')tercümanı çökertir.
Antti Haapala

1
@AnttiHaapala güzel bir örnek. Python yorumlayıcısında bir hata mı var? Her neyse, büyük girdi, örneğin kullanılarak önemsiz bir şekilde işlenir if len(expr) > 10000: raise ValueError.
jfs

1
@AnttiHaapala, len(expr)çek kullanılarak düzeltilemeyecek bir örnek verebilir misiniz ? Ya da Python uygulamasında hatalar var ve bu nedenle genel olarak güvenli kod yazmak imkansız mı?
jfs

13

eval()Ve * için bazı daha güvenli alternatifler :sympy.sympify().evalf()

* SymPy sympify, dokümantasyondaki aşağıdaki uyarıya göre de güvensizdir.

Uyarı: Bu işlevin kullandığını evalve bu nedenle temizlenmemiş girdi üzerinde kullanılmaması gerektiğini unutmayın.


10

Tamam, yani eval ile ilgili sorun, siz kurtulsanız bile sandbox'tan çok kolay bir şekilde çıkabilmesidir __builtins__. Sanal alandan kaçmak için kullanılan tüm yöntemler , bazı tehlikeli nesneler için izin verilen bazı nesneler ( veya benzerleri) aracılığıyla bir referans elde etmek için getattrveya object.__getattribute__( .operatör aracılığıyla ''.__class__.__bases__[0].__subclasses__) kullanmaya başlar. getattrayarlayarak elimine edilir __builtins__için None. object.__getattribute__zor olanıdır, çünkü kaldırılamaz, çünkü hem objectdeğişmezdir hem de onu kaldırmak her şeyi bozacaktır. Ancak, __getattribute__yalnızca .operatör aracılığıyla erişilebilir ; bu nedenle, eval'un korumalı alanından çıkmamasını sağlamak için girdinizden temizlemek yeterlidir.
Formülleri işlerken, ondalık basamağın tek geçerli kullanımı, ondan önce veya sonra gelmesidir[0-9], bu nedenle yalnızca diğer tüm örneklerini kaldırıyoruz ..

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

Python normalde 1 + 1.olduğu gibi davranırken 1 + 1.0, bunun sondaki kısmı kaldıracağını .ve sizi bırakacağını unutmayın 1 + 1. Sen ekleyebilir ), ve EOFtakip etmek izin şeylerin listesine ., ama neden rahatsız?


İlginç tartışmalı ilgili bir soru burada bulunabilir .
djvg

3
.Şu anda kaldırma ile ilgili argüman doğru olsun ya da olmasın , bu, Python'un gelecekteki sürümlerinin güvenli olmayan nesnelere veya işlevlere başka bir yoldan erişilmesine izin veren yeni sözdizimi sunması durumunda güvenlik açıkları potansiyeli bırakır. Bu çözüm Python 3.6 zaten güvensiz olduğu için şu saldırıya izin f-dizeleri ait: f"{eval('()' + chr(46) + '__class__')}". Kara listeye alma yerine beyaz listeye dayalı bir çözüm daha güvenli olacaktır, ancak bu sorunu hiç olmadan çözmek gerçekten daha iyidir eval.
kaya3

Bu, yeni güvenlik sorunlarını ortaya çıkaran gelecekteki dil özellikleri hakkında mükemmel bir noktadır.
Perkins

8

Ast modülünü kullanabilir ve her düğümün türünün bir beyaz listenin parçası olduğunu doğrulayan bir NodeVisitor yazabilirsiniz.

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)

Kara liste yerine beyaz liste aracılığıyla çalıştığı için güvenlidir. Erişebileceği tek işlev ve değişkenler, ona açıkça erişim verdiklerinizdir. İsterseniz bunlara kolayca erişim sağlayabilmeniz için matematikle ilgili işlevlerle bir dikte yerleştirdim, ancak bunu açıkça kullanmanız gerekir.

Dizge, sağlanmayan işlevleri çağırmaya çalışırsa veya herhangi bir yöntemi çağırırsa, bir istisna ortaya çıkar ve çalıştırılmaz.

Bu, Python'un yerleşik ayrıştırıcı ve değerlendiricisini kullandığından, aynı zamanda Python'un öncelik ve terfi kurallarını da miras alır.

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

Yukarıdaki kod yalnızca Python 3 üzerinde test edilmiştir.

İsterseniz, bu işleve bir zaman aşımı dekoratörü ekleyebilirsiniz.


7

Bunun nedeni evalve execçok tehlikeli olması, varsayılan compileişlevin herhangi bir geçerli python ifadesi için bayt kodu üretmesi ve varsayılanın evalveya execherhangi bir geçerli python bayt kodunu çalıştırmasıdır. Bugüne kadarki tüm cevaplar, üretilebilecek bayt kodunu kısıtlamaya (girdiyi temizleyerek) veya AST'yi kullanarak kendi alana özgü dilinizi oluşturmaya odaklandı.

Bunun yerine, evalhaince bir şey yapamayan basit bir işlevi kolayca oluşturabilirsiniz ve bellek veya kullanılan zaman üzerinde kolayca çalışma zamanı kontrolleri yapabilirsiniz. Tabii basit matematikse, kısayol olduğundan daha var.

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]

Bunun çalışma şekli basittir, herhangi bir sabit matematiksel ifade derleme sırasında güvenli bir şekilde değerlendirilir ve sabit olarak saklanır. Derleme tarafından döndürülen kod nesnesi d, bunun bayt kodu olan LOAD_CONSTve ardından yüklenecek sabitin numarası (genellikle listedeki sonuncusu) ve ardından Sbayt kodu olan RETURN_VALUE. Bu kısayol çalışmazsa, kullanıcı girdisinin sabit bir ifade olmadığı anlamına gelir (bir değişken veya işlev çağrısı veya benzeri içerir).

Bu aynı zamanda bazı daha karmaşık girdi biçimlerine de kapı açar. Örneğin:

stringExp = "1 + cos(2)"

Bu aslında bayt kodunu değerlendirmeyi gerektirir ki bu hala oldukça basittir. Python bayt kodu yığın yönelimli bir dildir, bu nedenle her şey basit TOS=stack.pop(); op(TOS); stack.put(TOS)veya benzerdir. Anahtar, yalnızca güvenli olan (değerleri yükleme / depolama, matematik işlemleri, değerleri döndürme) ve güvenli olmayanları (öznitelik arama) olmayan işlem kodlarını uygulamaktır. Kullanıcının işlevleri çağırabilmesini istiyorsanız (yukarıdaki kısayolu kullanmama nedeninin tamamı), basitçe uygulamanızı CALL_FUNCTIONyalnızca 'güvenli' bir listedeki işlevlere izin verin.

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

Açıkçası, bunun gerçek versiyonu biraz daha uzun olacaktır (24'ü matematikle ilgili olan 119 işlem kodu vardır). Eklemek STORE_FASTve birkaç tane daha eklemek 'x=5;return x+x, benzer veya benzer girdilere önemsiz bir şekilde kolayca izin verir . Kullanıcı tarafından oluşturulan işlevlerin kendileri VMeval aracılığıyla yürütüldüğü sürece, kullanıcı tarafından oluşturulan işlevleri yürütmek için bile kullanılabilir (onları çağrılabilir hale getirmeyin !!! yoksa bir yerde geri arama olarak kullanılabilirler). Döngüleri gotoişlemek, bayt kodlar için destek gerektirir , bu da bir foryineleyiciden whilegeçerli talimata bir göstericiyi sürdürmek anlamına gelir , ancak çok zor değildir. DOS'a direnç için, ana döngü hesaplamanın başlangıcından bu yana ne kadar zaman geçtiğini kontrol etmeli ve bazı operatörler makul bir sınırın (BINARY_POWER en bariz olmak).

Bu yaklaşım, basit ifadeler için basit bir dilbilgisi ayrıştırıcısından biraz daha uzun olsa da (sadece derlenmiş sabiti yakalama hakkında yukarıya bakın), daha karmaşık girdilere kolayca yayılır ve dilbilgisi ile uğraşmayı gerektirmez ( compilerastgele karmaşık olan herhangi bir şeyi alın ve basit talimatlar dizisi).


6

Sanırım kullanırım eval(), ancak önce dizenin kötü niyetli bir şeyin aksine geçerli bir matematiksel ifade olup olmadığını kontrol ederdim. Doğrulama için bir normal ifade kullanabilirsiniz.

eval() ayrıca daha fazla güvenlik için içinde çalıştığı ad alanını kısıtlamak için kullanabileceğiniz ek argümanlar alır.


3
Ama tabii ki, rastgele matematiksel ifadeleri doğrulamak için normal ifadelere güvenmeyin.
Yüksek Performans Mark

@ Yüksek Performans Markası: Evet, aklında ne tür matematiksel ifadelere bağlı olduğunu tahmin ediyorum. . . örneğin sadece basit sayılarla aritmetik ve +, -, *, /, **, (, )ya da bir şey daha karmaşık
Tim Goodman

@Tim - endişelendiğim () veya daha doğrusu (((((()))))). Gerçekte, OP'nin onlar için endişelenmesi gerektiğini düşünüyorum, kaşlarım OP'nin sorunları tarafından açılmıyor.
Yüksek Performans Mark

2
Ad eval()alanını kısıtlasanız bile girişi kontrol etmiyorsanız kullanmayın , örneğin eval("9**9**9**9**9**9**9**9", {'__builtins__': None})CPU, bellek tüketir.
jfs 04

3
Eval ad alanını kısıtlamak güvenliğe katkıda bulunmaz .
Antti Haapala

5

Bu çok geç bir cevap, ancak ileride başvurmak için faydalı olduğunu düşünüyorum. Kendi matematik ayrıştırıcınızı yazmak yerine (yukarıdaki örnek harika olsa da) SymPy'yi kullanabilirsiniz. Bununla ilgili çok fazla deneyimim yok, ancak belirli bir uygulama için herkesin yazabileceğinden çok daha güçlü bir matematik motoru içeriyor ve temel ifade değerlendirmesi çok kolay:

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

Gerçekten çok havalı! A from sympy import *, trigonometrik işlevler, özel işlevler vb. Gibi çok daha fazla işlev desteği getiriyor, ancak burada neyin nereden geldiğini göstermek için bundan kaçındım.


3
Sympy "güvenli" mi? Aynı şekilde yararlanılabilecek eval () etrafında bir sarmalayıcı olduğunu öneren çok sayıda gönderi var gibi görünüyor . Ayrıca evalfuyuşuk ndarrays almaz.
Mark Mikofski

14
Güvenilmeyen girişler için hiçbir sempya güvenli değildir. Onun yerine geçtiğim sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")bu aramaları deneyin . Dizin muhtemelen diğer bilgisayarlarda farklı olacaktır. Bu Ned Batchelder istismarının bir çeşididirsubprocess.Popen()lsrm -rf /
Mark Mikofski

1
Gerçekten de, güvenliğe hiç bir katkı sağlamaz.
Antti Haapala

4

[Bunun eski bir soru olduğunu biliyorum, ancak ortaya çıktıkça yeni yararlı çözümlere işaret etmeye değer]

Python3.6 beri bu yetenek şimdi olduğu dile yerleşik icat, "f-dizeleri" .

Bakınız: PEP 498 - Değişmez Dizi Enterpolasyonu

Örneğin ( föneki not edin ):

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

7
Çok ilginç bağlantı. Ama sanırım f-dizeleri kaynak kodu yazmayı kolaylaştırmak için buradayken, soru değişkenlerin içindeki dizelerle (muhtemelen güvenilmeyen kaynaklardan) çalışmakla ilgili görünüyor. Bu durumda f dizeleri kullanılamaz.
Bernhard

2 + 4 veya 2 * 4 veya 2-4 veya vs yapmaya artık operatörü atayabilirsiniz f '{2 {operatör} 4}' etkisi için bir şeyler yapmak için herhangi bir yol yoktur
Skyler

Bu, pratik olarak sadece yapmaya eşdeğerdir str(eval(...)), bu yüzden kesinlikle daha güvenli değildir eval.
kaya3

Exec / eval ile aynı görünüyor ...
Victor VosMottor, Monica'ya teşekkürler

0

evalTemiz bir ad alanında kullanın :

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

Temiz ad alanı, enjeksiyonu engellemelidir. Örneğin:

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

Aksi takdirde şunları elde edersiniz:

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

Matematik modülüne erişim vermek isteyebilirsiniz:

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

35
eval ("(1) .__ sınıf __.__ bazlar __ [0] .__ alt sınıfları __ () [81] ('echo geçildi'.split ())", {' yerleşikler ': Yok}) # korumalı alanınızdan çıkar
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})burne kabuğunu çalıştırır ...
Antti Haapala

8
Bu güvenli değil . Kötü amaçlı kod yine de yürütülebilir.
Fermi paradoksu

This is not safe- bence genel olarak bash kullanmak kadar güvenli. BTW: eval('math.sqrt(2.0)')<- "matematik." yukarıda yazıldığı gibi gereklidir.
Hannu

0

İşte eval kullanmadan soruna çözümüm. Python2 ve Python3 ile çalışır. Negatif sayılarla çalışmaz.

$ 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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.