uso - qué función de python se utiliza para el resultado de la consola
Evaluar una expresión matemática en una cadena (13)
eval
es malvado
eval("__import__(''os'').remove(''important file'')") # arbitrary commands
eval("9**9**9**9**9**9**9**9", {''__builtins__'': None}) # CPU, memory
Nota: incluso si usa set __builtins__
en None
, aún podría ser posible usar introspection:
eval(''(1).__class__.__bases__[0].__subclasses__()'', {''__builtins__'': None})
Evaluar la expresión aritmética usando 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)
Puede limitar fácilmente el rango permitido para cada operación o cualquier resultado intermedio, por ejemplo, para limitar los argumentos de entrada para 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
O para limitar la magnitud de los resultados intermedios:
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_)
Ejemplo
>>> 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:
stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
Esto devuelve el siguiente error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: ''2^4''
Sé que eval
puede eval
esto, pero ¿no hay un método mejor y, lo que es más importante, más seguro para evaluar una expresión matemática que se almacena en una cadena?
Algunas alternativas más seguras a eval()
y sympy.sympify().evalf()
* :
* La sympy.sympify().evalf() SymPy sympy.sympify().evalf() es segura de acuerdo con la siguiente advertencia de la documentación.
Advertencia: Tenga en cuenta que esta función usa
eval
, y por lo tanto no debe usarse en entradas no optimizadas.
Creo que usaría eval()
, pero primero verificaría que la cadena sea una expresión matemática válida, a diferencia de algo malicioso. Puede usar una expresión regular para la validación.
eval()
también toma argumentos adicionales que puedes usar para restringir el espacio de nombres en el que opera para mayor seguridad.
De acuerdo, entonces el problema con eval es que puede escapar de su sandbox con demasiada facilidad, incluso si te deshaces de __builtins__
. Todos los métodos para escapar del sandbox se reducen a usar getattr
u object.__getattribute__
(a través del operador .
) Para obtener una referencia a algún objeto peligroso a través de algún objeto permitido ( ''''.__class__.__bases__[0].__subclasses__
o similar). getattr
se elimina estableciendo __builtins__
en None
. object.__getattribute__
es el más difícil, ya que no se puede eliminar simplemente, porque el object
es inmutable y porque eliminarlo lo rompería todo. Sin embargo, __getattribute__
solo es accesible a través de .
operador, por lo que purgar eso de su entrada es suficiente para garantizar que eval no puede escapar de su entorno limitado.
Al procesar fórmulas, el único uso válido de un decimal es cuando está precedido o seguido por [0-9]
, por lo que eliminamos todas las demás instancias de .
.
import re
inp = re.sub(r"/.(?![0-9])","", inp)
val = eval(inp, {''__builtins__'':None})
Tenga en cuenta que mientras que python normalmente trata 1 + 1.
como 1 + 1.0
, esto eliminará el final .
y te dejo con 1 + 1
. Usted podría agregar )
, y
EOF
a la lista de cosas permitidas a seguir .
, pero ¿para qué molestarse?
Esta es una respuesta tardía masiva, pero creo útil para futuras referencias. En lugar de escribir su propio analizador matemático (aunque el ejemplo anterior de pyparsing es genial) podría usar SymPy. No tengo mucha experiencia con eso, pero contiene un motor matemático mucho más potente de lo que cualquiera podría escribir para una aplicación específica y la evaluación de la expresión básica es muy fácil:
>>> import sympy
>>> x, y, z = sympy.symbols(''x y z'')
>>> sympy.sympify("x**3 + sin(y)").evalf(subs={x:1, y:-3})
0.858879991940133
Muy bueno! A from sympy import *
brinda mucho más soporte de funciones, como funciones trigonométricas, funciones especiales, etc., pero lo he evitado aquí para mostrar lo que viene de dónde.
La razón por la que eval
y exec
son tan peligrosos es que la función de compile
predeterminada generará bytecode para cualquier expresión válida de python, y el eval
o exec
predeterminado ejecutará cualquier bytecode de python válido. Todas las respuestas hasta la fecha se han centrado en restringir el bytecode que se puede generar (mediante la desinfección de la entrada) o crear su propio idioma específico del dominio utilizando AST.
En su lugar, puede crear fácilmente una función simple de eval
que sea incapaz de hacer algo nefasto y que pueda tener fácilmente controles de tiempo de ejecución en la memoria o el tiempo utilizado. Por supuesto, si es matemática simple, entonces hay un atajo.
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]
La forma en que esto funciona es simple, cualquier expresión matemática constante se evalúa de forma segura durante la compilación y se almacena como una constante. El objeto de código devuelto por compilación consta de d
, que es el bytecode de LOAD_CONST
, seguido del número de la constante a cargar (generalmente el último en la lista), seguido de S
, que es el bytecode para RETURN_VALUE
. Si este atajo no funciona, significa que la entrada del usuario no es una expresión constante (contiene una variable o llamada de función o similar).
Esto también abre la puerta a algunos formatos de entrada más sofisticados. Por ejemplo:
stringExp = "1 + cos(2)"
Esto requiere en realidad evaluar el bytecode, que todavía es bastante simple. Python bytecode es un lenguaje orientado a la pila, así que todo es una simple cuestión de TOS=stack.pop(); op(TOS); stack.put(TOS)
TOS=stack.pop(); op(TOS); stack.put(TOS)
TOS=stack.pop(); op(TOS); stack.put(TOS)
o similar. La clave es implementar únicamente los códigos de operación que son seguros (cargar / almacenar valores, operaciones matemáticas, devolver valores) y no inseguros (búsqueda de atributos). Si desea que el usuario pueda llamar a funciones (la razón completa para no utilizar el acceso directo anterior), simplemente haga que su implementación de CALL_FUNCTION
solo permita funciones en una lista ''segura''.
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''))
Obviamente, la versión real de esto sería un poco más larga (hay 119 códigos de operación, 24 de los cuales están relacionados con las matemáticas). Agregar STORE_FAST
y un par de otros permitiría la entrada como ''x=5;return x+x
o similar, trivialmente fácil. Incluso se puede usar para ejecutar funciones creadas por el usuario, siempre que las funciones creadas por el usuario se ejecuten a través de VMeval (¡no las haga llamables!) O podrían ser utilizadas como una devolución de llamada en algún lugar. El manejo de bucles requiere soporte para los goto
bytes goto
, lo que significa cambiar de un iterador a while
y mantener un puntero a la instrucción actual, pero no es demasiado difícil. Para la resistencia a DOS, el ciclo principal debería verificar cuánto tiempo ha pasado desde el inicio del cálculo, y ciertos operadores deben denegar la entrada por encima de un límite razonable (siendo BINARY_POWER
el más obvio).
Si bien este enfoque es algo más extenso que un simple analizador gramatical para expresiones simples (ver más arriba simplemente capturando la constante compilada), se extiende fácilmente a entradas más complicadas, y no requiere lidiar con la gramática ( compile
tomar cualquier cosa arbitrariamente complicada y reducirla a una secuencia de instrucciones simples).
Puede usar el módulo ast y escribir un NodeVisitor que verifique que el tipo de cada nodo sea parte de una lista blanca.
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)
Como funciona a través de una lista blanca en lugar de una lista negra, es seguro. Las únicas funciones y variables a las que puede acceder son aquellas a las que explícitamente le da acceso. Completé un dict con funciones relacionadas con las matemáticas para que pueda facilitar el acceso a ellas si lo desea, pero tiene que usarlo explícitamente.
Si la cadena intenta llamar a funciones que no se han proporcionado, o invocar cualquier método, se generará una excepción y no se ejecutará.
Como esto usa el analizador y el analizador integrados de Python, también hereda las reglas de precedencia y promoción de Python.
>>> evaluate("7 + 9 * (2 << 2)")
79
>>> evaluate("6 // 2 + 0.0")
3.0
El código anterior solo se ha probado en Python 3.
Si lo desea, puede agregar un decorador de tiempo de espera en esta función.
Python ya tiene una función para evaluar con seguridad cadenas que contienen expresiones literales:
Si no desea utilizar eval, entonces la única solución es implementar el analizador gramatical apropiado. Eche un vistazo a Pyparsing .
Si ya estás usando wolframalpha, tienen una api de python, que te permite evaluar expresiones. Puede ser un poco lento, pero al menos muy preciso.
Use eval
en un espacio de nombres limpio:
>>> ns = {''__builtins__'': None}
>>> eval(''2 ** 4'', ns)
16
El espacio de nombre limpio debe evitar la inyección. Por ejemplo:
>>> 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__''
De lo contrario, obtendrías:
>>> eval(''__builtins__.__import__("os").system("echo got through")'')
got through
0
Es posible que desee dar acceso al módulo de matemáticas:
>>> import math
>>> ns = vars(math).copy()
>>> ns[''__builtins__''] = None
>>> eval(''cos(pi/3)'', ns)
0.50000000000000011
Pyparsing se puede usar para analizar expresiones matemáticas. En particular, fourFn.py muestra cómo analizar expresiones aritméticas básicas. A continuación, he vuelto a envolver fourFn en una clase de analizador numérico para facilitar su reutilización.
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
Puedes usarlo así
nsp = NumericStringParser()
result = nsp.eval(''2^4'')
print(result)
# 16.0
result = nsp.eval(''exp(2^4)'')
print(result)
# 8886110.520507872
[Sé que esta es una vieja pregunta, pero vale la pena señalar nuevas soluciones útiles a medida que aparecen]
Desde python3.6, esta capacidad ahora está integrada en el lenguaje , acuñado "f-strings" .
Ver: PEP 498 - Interpolación de cadenas literales
Por ejemplo (tenga en cuenta el prefijo f
):
f''{2**4}''
=> ''16''