Следующие вопросы
- Вычисление математического выражения в строке
- Анализ уравнений в Python
- Безопасный способ синтаксического анализа пользовательских математических формул в Python
- Оценить математические уравнения на основе небезопасного ввода пользователя в Python
и их соответствующие ответы заставили меня задуматься, как я могу проанализировать одно математическое выражение (в общих чертах в соответствии со строками этого ответа https://stackoverflow.com/a/594294/1672565), заданный (более или менее доверенным) пользователем, эффективно для 20-30 тысяч входных значений, поступающих из базы данных. Я реализовал быстрый и грязный тест, чтобы сравнивать разные решения.
# Runs with Python 3(.4)
import pprint
import time
# This is what I have
userinput_function = '5*(1-(x*0.1))' # String - numbers should be handled as floats
demo_len = 20000 # Parameter for benchmark (20k to 30k in real life)
print_results = False
# Some database, represented by an array of dicts (simplified for this example)
database_xy = []
for a in range(1, demo_len, 1):
database_xy.append({
'x':float(a),
'y_eval':0,
'y_sympya':0,
'y_sympyb':0,
'y_sympyc':0,
'y_aevala':0,
'y_aevalb':0,
'y_aevalc':0,
'y_numexpr': 0,
'y_simpleeval':0
})
# Решение №1: eval [да, совершенно небезопасно]
time_start = time.time()
func = eval("lambda x: " + userinput_function)
for item in database_xy:
item['y_eval'] = func(item['x'])
time_end = time.time()
if print_results:
pprint.pprint(database_xy)
print('1 eval: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение № 2a: sympy - evalf (http://www.sympy.org) strong >
import sympy
time_start = time.time()
x = sympy.symbols('x')
sympy_function = sympy.sympify(userinput_function)
for item in database_xy:
item['y_sympya'] = float(sympy_function.evalf(subs={x:item['x']}))
time_end = time.time()
if print_results:
pprint.pprint(database_xy)
print('2a sympy: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение № 2b: sympy - lambdify (http://www.sympy.org) strong >
from sympy.utilities.lambdify import lambdify
import sympy
import numpy
time_start = time.time()
sympy_functionb = sympy.sympify(userinput_function)
func = lambdify(x, sympy_functionb, 'numpy') # returns a numpy-ready function
xx = numpy.zeros(len(database_xy))
for index, item in enumerate(database_xy):
xx[index] = item['x']
yy = func(xx)
for index, item in enumerate(database_xy):
item['y_sympyb'] = yy[index]
time_end = time.time()
if print_results:
pprint.pprint(database_xy)
print('2b sympy: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение № 2c: sympy - lambdify с numexpr [и numpy] (http://www.sympy.org а>)
from sympy.utilities.lambdify import lambdify
import sympy
import numpy
import numexpr
time_start = time.time()
sympy_functionb = sympy.sympify(userinput_function)
func = lambdify(x, sympy_functionb, 'numexpr') # returns a numpy-ready function
xx = numpy.zeros(len(database_xy))
for index, item in enumerate(database_xy):
xx[index] = item['x']
yy = func(xx)
for index, item in enumerate(database_xy):
item['y_sympyc'] = yy[index]
time_end = time.time()
if print_results:
pprint.pprint(database_xy)
print('2c sympy: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение № 3a: asteval [на основе ast] - со строковой магией (http://newville.github.io/asteval/index.html)
from asteval import Interpreter
aevala = Interpreter()
time_start = time.time()
aevala('def func(x):\n\treturn ' + userinput_function)
for item in database_xy:
item['y_aevala'] = aevala('func(' + str(item['x']) + ')')
time_end = time.time()
if print_results:
pprint.pprint(database_xy)
print('3a aeval: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение № 3b (М. Ньювилл): asteval [на основе ast] - проанализировать и запустить (http://newville.github.io/asteval/index.html)
from asteval import Interpreter
aevalb = Interpreter()
time_start = time.time()
exprb = aevalb.parse(userinput_function)
for item in database_xy:
aevalb.symtable['x'] = item['x']
item['y_aevalb'] = aevalb.run(exprb)
time_end = time.time()
print('3b aeval: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение № 3c (М. Ньювилл): asteval [на основе ast] - проанализировать и запустить с помощью numpy (http://newville.github.io/asteval/index.html)
from asteval import Interpreter
import numpy
aevalc = Interpreter()
time_start = time.time()
exprc = aevalc.parse(userinput_function)
x = numpy.array([item['x'] for item in database_xy])
aevalc.symtable['x'] = x
y = aevalc.run(exprc)
for index, item in enumerate(database_xy):
item['y_aevalc'] = y[index]
time_end = time.time()
print('3c aeval: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение №4: simpleeval [на основе ast] (https://github.com/danthedeckie/simpleeval)
from simpleeval import simple_eval
time_start = time.time()
for item in database_xy:
item['y_simpleeval'] = simple_eval(userinput_function, names={'x': item['x']})
time_end = time.time()
if print_results:
pprint.pprint(database_xy)
print('4 simpleeval: ' + str(round(time_end - time_start, 4)) + ' seconds')
# Решение № 5 numexpr [и numpy] (https://github.com/pydata/numexpr)
import numpy
import numexpr
time_start = time.time()
x = numpy.zeros(len(database_xy))
for index, item in enumerate(database_xy):
x[index] = item['x']
y = numexpr.evaluate(userinput_function)
for index, item in enumerate(database_xy):
item['y_numexpr'] = y[index]
time_end = time.time()
if print_results:
pprint.pprint(database_xy)
print('5 numexpr: ' + str(round(time_end - time_start, 4)) + ' seconds')
На моей старой тестовой машине (Python 3.4, Linux 3.11 x86_64, два ядра, 1,8 ГГц) я получил следующие результаты:
1 eval: 0.0185 seconds
2a sympy: 10.671 seconds
2b sympy: 0.0315 seconds
2c sympy: 0.0348 seconds
3a aeval: 2.8368 seconds
3b aeval: 0.5827 seconds
3c aeval: 0.0246 seconds
4 simpleeval: 1.2363 seconds
5 numexpr: 0.0312 seconds
Что бросается в глаза, так это невероятная скорость eval, хотя я не хочу использовать это в реальной жизни. Вторым лучшим решением, по-видимому, является numexpr, который зависит от numpy - зависимости, которую я бы хотел избежать, хотя это не является жестким требованием. Следующим лучшим вариантом является simpleeval, построенный на основе ast. aeval, другое решение на основе Ast, страдает тем фактом, что мне нужно сначала преобразовать каждое отдельное входное значение с плавающей запятой в строку, и я не мог найти способа. sympy изначально был моим фаворитом, потому что он предлагает наиболее гибкое и, по всей видимости, самое безопасное решение, но в итоге оказался последним с впечатляющим расстоянием до предпоследнего решения.
Обновление 1: существует гораздо более быстрый подход с использованием sympy. См. Решение 2b. Он почти так же хорош, как numexpr, хотя я не уверен, действительно ли sympy использует его для внутренних целей.
Обновление 2: реализации sympy теперь используют sympify вместо simpleify (как рекомендовано его ведущим разработчиком, asmeurer - Благодарность). Он не использует numexpr, если его не попросят сделать это явно (см. Решение 2c). Я также добавил два значительно более быстрых решения на основе asteval (спасибо M Newville).
Какие варианты у меня есть, чтобы еще больше ускорить использование относительно безопасных решений? Существуют ли другие, безопасные (-истские) подходы, например, с прямым использованием ast?
eval
/compile
? (Однако это не предотвращает DoS.) - person Ry-♦   schedule 06.12.2015eval
; он делает свою интерпретацию. github.com/danthedeckie/simpleeval/blob/master/simpleeval.py - person Ry-♦   schedule 06.12.2015lambdify
не использует numexpr, если вы не установитеmodules='numexpr'
. - person asmeurer   schedule 08.12.2015sympify()
почти небезопасно какeval()
. Не могли бы вы также добавить для них записку? - person user   schedule 03.03.2016|a-b|
для представленияabs(a-b)
, а также как выражения отложенной оценки, которые соответствуют вашим предварительно скомпилированным выражениям. У plusminus есть онлайн-демонстрация, открытая для Интернета, которую вы можете опробовать. - person PaulMcG   schedule 08.03.2021