Вычисления значений выражений с помощью Python
Вычисление выражений: обзор методов
Как вычислить значение математического выражения, введённого пользователем, с минимальным риском для безопасности?
Наиболее эффективным решением для типовых задач является использование встроенной функции eval с ограниченным пространством имён. Это позволяет выполнить арифметические операции, а также вызывать функции из модуля math, одновременно блокируя доступ к опасным объектам Python.
import math
def safe_eval(expr):
allowed = {k: v for k, v in math.__dict__.items() if not k.startswith('_')}
allowed['__builtins__'] = None
return eval(expr, allowed, {})
print(safe_eval('sin(pi/4)**2 + cos(pi/4)**2')) # 1.0
print(safe_eval('sqrt(144)')) # 12.0Python решение примера (решение примера на python)
Важно
При таком подходе пользователь не сможет обратиться к функциям ввода-вывода, импорту или системным командам. Для дополнительной защиты можно парсить выражение через ast.parse и проверять допустимые узлы.
Типичные ошибки
- SyntaxError – неверный синтаксис выражения. Решение: обернуть вызов в try-except.
- NameError – использование неизвестных имён (например, переменных, не переданных в словарь). Решение: определить все допустимые имена в
allowedили передавать переменные через второй аргумент. - ZeroDivisionError – деление на ноль. Решение: обработать исключение или использовать модуль
decimalдля контролируемой точности.
Цель – выполнение произвольных формул, вводимых извне, на сервере или в десктопном приложении, где важна скорость и относительная безопасность.
Когда безопасность критична и выражение состоит только из литералов?
Используйте ast.literal_eval. Он умеет вычислять строки, числа, кортежи, списки, словари, множества и булевы значения, но не операторы. Подходит для разбора JSON-подобных данных, конфигурационных файлов.
import ast
expr = "[1, 2, 3]"
result = ast.literal_eval(expr)
print(result, type(result)) # [1, 2, 3] <class 'list'>Python вычисление значения выражений (вычисление значения выражений в python)
Ошибка: ValueError при попытке вычислить выражение с операторами. Решение: не использовать literal_eval для формул с арифметикой.
Как выполнить символьное вычисление, например, найти производную или интеграл?
Применяйте библиотеку sympy. Она позволяет работать с математическими символами и выполнять аналитические преобразования.
from sympy import symbols, diff, sin, cos, lambdify
x = symbols('x')
func = sin(x)**2 + cos(x)**2
diff_func = diff(func, x)
print(diff_func) # 0
# численное вычисление
f_num = lambdify(x, func, 'numpy')
print(f_num(3.14)) # 1.0
вычисление функции в python (вычисление значения функции в python)
Проблема: sympy медленнее eval при большом количестве подстановок. Используйте lambdify для преобразования в быструю численную функцию.
Цель – математическое моделирование, проверка тождеств, обучение.
Как вычислить выражение с векторизацией для больших массивов данных?
Библиотека numexpr компилирует выражение в эффективный машинный код и работает с массивами NumPy.
import numexpr as ne
import numpy as np
a = np.random.rand(1000000)
b = np.random.rand(1000000)
result = ne.evaluate('a + b * sin(a)')Python вычисление корня (вычисление квадратного корня в python)
Ошибка: если переменные не являются массивами одинаковой длины. Решение: привести к одному типу или использовать вещание.
Цель – ускорение вычислений над крупными наборами данных.
Как реализовать собственный парсер выражений для учебных целей или специфических ограничений?
Можно написать алгоритм сортировочной станции (Дейкстра) для преобразования инфиксной записи в обратную польскую (RPN) и затем вычислить RPN с помощью стека. Это даёт полный контроль над операторами и функциями.
def rpn(expr):
# упрощённая реализация для +, -, *, /
...
# (полный код опущен для краткости)
Сложность: поддержка унарных минусов, функций с переменным числом аргументов. Решение: тщательная обработка токенов.
Цель – обучение, встраивание в продукты с очень жёсткими требованиями безопасности.
Расширенные примеры вычислений
Пример 1: безопасный eval с передачей переменных
import math
def var_eval(expr, **variables):
allowed = {k: v for k, v in math.__dict__.items() if not k.startswith('_')}
allowed['__builtins__'] = None
allowed.update(variables)
return eval(expr, allowed, {})
result = var_eval('a * b + c', a=2, b=3, c=4)
print(result) # 1010
Таким образом можно вычислять формулы с любыми переменными, не опасаясь доступа к системным функциям.
Пример 2: использование ast для парсинга и разрешения только определённых операций
import ast
def safe_eval_ast(expr, allowed_nodes=None):
if allowed_nodes is None:
allowed_nodes = {ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant,
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.USub}
tree = ast.parse(expr, mode='eval')
for node in ast.walk(tree):
if type(node) not in allowed_nodes:
raise ValueError(f"Запрещённый узел: {type(node)}")
return eval(compile(tree, '', 'eval'), {'__builtins__': None}, {})
print(safe_eval_ast('3 + 4 * 2')) # 11
print(safe_eval_ast('[1,2,3]')) # ValueError (запрещён List) 11 Traceback ... ValueError: Запрещённый узел: <class 'ast.List'>
Данный подход позволяет гибко ограничивать использование Python-синтаксиса.
Пример 3: символьная производная и подстановка значений через sympy
from sympy import symbols, diff, sin, exp, lambdify
x, y = symbols('x y')
func = sin(x) * exp(-y**2)
derivative = diff(func, x)
print(derivative) # exp(-y**2)*cos(x)
f_num = lambdify((x, y), func, 'numpy')
val = f_num(0.5, 1.0)
print(val)exp(-y**2)*cos(x) 0.306054... # численное значение
Символьные вычисления полезны для аналитического упрощения выражений.
Пример 4: вычисление выражений с условным оператором (тернарник) через eval
expr = '10 if a > 5 else 20'
result = eval(expr, {'__builtins__': None}, {'a': 7})
print(result) # 1010
С помощью eval можно выполнять условные конструкции, но подобные выражения следует тщательно проверять на безопасность.
Пример 5: парсер обратной польской записи (RPN) для четырёх арифметических операций
def tokenize(expr):
import re
return re.findall(r'\d+\.?\d*|[+\-*/()]', expr)
def to_rpn(tokens):
# реализация сортировочной станции (упрощённо)
pass # полный код не приводится
def eval_rpn(rpn):
stack = []
for tok in rpn:
if tok in '+-*/':
b = stack.pop()
a = stack.pop()
if tok == '+': stack.append(a+b)
elif tok == '-': stack.append(a-b)
elif tok == '*': stack.append(a*b)
elif tok == '/': stack.append(a/b)
else:
stack.append(float(tok))
return stack[0]
# Пример использования
expr = '3 + 4 * 2'
tokens = tokenize(expr)
rpn = to_rpn(tokens)
print(eval_rpn(rpn)) # 1111
Такой парсер даёт полный контроль над операциями и не зависит от встроенного eval, что полезно в окружениях с жёсткими ограничениями.