Преобразование строки в Python выражение (программа падает при слишком больших числах)
Есть пользователь который вводит выражение типа "100 + 100 / 10", которое необходимо привести в некое число преобразовав в питоновское выражение.
Для парсинга использую py_expression_eval:
from py_expression_eval import Parser
parser = Parser()
parser.parse("100 + 100 / 10").evaluate({})
Пишу команду !math для Discord бота на hikari & hikari-lightbulb.
Проблема в том, что при вводе выражения типа 100000000000000000 ^ 100000000000000000 бот ложится не выбрасывая абсолютно никаких ошибок.
Пробовал ещё numexpr, но тоже самое.
С чем может быть связано? Как обрабатывать такие "краш-числа"?
UPD: Да не нужно мне эти числа вычислять! Мне нужно защитить программу от ввода пользователя.
Ответы (2 шт):
Как я уже написал в комментариях, вычисление таких степеней очень затратно и интерпретатор питона на них просто подвисает. Вот вам пример, что с этим можно сделать с помощью таймаут-декоратора, который нужно предварительно установить (их на самом деле много разных написано, я выбрал первый попавшийся в поиске):
pip install timeout-decorator
Ну и библиотеку парсинга выражений заодно напишу как ставить для полной воспроизводимости кода:
pip install py_expression_eval
Этим кодом я покажу, какое время считаются разные степени и какой длины получаются числа в результате этих вычислений, ну и как использовать декоратор таймаута:
import sys
import math
import time
import timeout_decorator
from py_expression_eval import Parser
# без увеличения этого значения вычисление выражений падает
sys.set_int_max_str_digits(1_000_000_000)
@timeout_decorator.timeout(5)
def mycalc(n):
parser = Parser()
return parser.parse(n).evaluate({})
def mytest():
try:
for i in range(10):
t1 = time.perf_counter()
n = 10 ** i
s = f"{n} ^ {n}"
x = mycalc(s)
t2 = time.perf_counter()
t = t2-t1
l = 1 + int(math.log10(x))
print(f'Шаг: {i}, Вычисление: {s:^20}, Время: {t:.6f}, Получено цифр: {l}', flush=True)
except TimeoutError:
print(f'Шаг: {i}, Вычисление: {s:^20}, Превышено допустимое время!')
if __name__ == '__main__':
mytest()
Результат работы кода:
Шаг: 0, Вычисление: 1 ^ 1 , Время: 0.007594, Получено цифр: 1
Шаг: 1, Вычисление: 10 ^ 10 , Время: 0.000222, Получено цифр: 11
Шаг: 2, Вычисление: 100 ^ 100 , Время: 0.000238, Получено цифр: 201
Шаг: 3, Вычисление: 1000 ^ 1000 , Время: 0.000255, Получено цифр: 3001
Шаг: 4, Вычисление: 10000 ^ 10000 , Время: 0.003241, Получено цифр: 40001
Шаг: 5, Вычисление: 100000 ^ 100000 , Время: 0.095435, Получено цифр: 500001
Шаг: 6, Вычисление: 1000000 ^ 1000000 , Время: 4.649145, Получено цифр: 6000001
Шаг: 7, Вычисление: 10000000 ^ 10000000 , Превышено допустимое время!
Количество цифр я считаю не через len(str(x)), а через десятичный логарифм, потому что этот подсчёт без установки параметра int_max_str_digits вообще падает, а если его поставить побольше, как у меня и сделано (для работы библиотеки парсинга выражений), то уже это вычисление начинает работать очень долго, поэтому пришлось через логарифм, но логарифм всё-равно даёт тот же результат, только моментально, я проверял на небольших значениях переменной цикла, да и просто по логике.
Доставьте память в компьютер, чтобы не было свопа.
Сколько памяти потребуется чтобы сохранить 100000000000000000100000000000000000 ?
Питон хранит длинные целые в виде цепочек из 30-битных слов. Каждое слово занимает 4 байта. Число n в таком представлении занимает 4·log230(n) байт. Вычислим место :
4·log230(100000000000000000100000000000000000) =
= 4·ln(100000000000000000100000000000000000) / ln(230) =
= 4·100000000000000000·ln(100000000000000000) / ln(230) ≈
≈ 4·1017·39.14 / 20.79 байт ≈ 7.53·1017 байт = 753 петабайт
Архитектуру компьютера менять не надо, 64 разрядов достаточно чтобы адресовать такое количество памяти (264 ≈ 1.64·1019 > 7.53·1017). Память придётся докупить. При текущих ценах на память - 21 миллиард рублей за 5.5 миллионов планок по 128 гигабайт каждая.