106 lines
4.4 KiB
Python
106 lines
4.4 KiB
Python
# Создано: 2026-04-10
|
||
# Изменено: 2026-03-23 — упрощён до поля ввода выражения (демонстрация деплоя).
|
||
# Принимает произвольное математическое выражение: "2+2*(3-1)", "(10/3)**2" и т.д.
|
||
# GET → HTML страница с формой; POST с {expr} → вычисление через безопасный eval.
|
||
# Безопасность eval: __builtins__=None, только math-функции в locals.
|
||
|
||
import math
|
||
|
||
_PAGE = """<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Калькулятор — Python 3.11</title>
|
||
<style>
|
||
body { font-family: monospace; background: #0f172a; color: #e2e8f0;
|
||
display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }
|
||
.box { background: #1e293b; border-radius: 12px; padding: 32px; width: 420px; box-shadow: 0 8px 32px #0005; }
|
||
h2 { margin: 0 0 4px; font-size: 20px; color: #7dd3fc; }
|
||
.sub { color: #475569; font-size: 12px; margin-bottom: 24px; }
|
||
input { width: 100%; box-sizing: border-box; padding: 10px 14px; font-size: 18px; font-family: monospace;
|
||
background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #f1f5f9; outline: none; }
|
||
input:focus { border-color: #38bdf8; }
|
||
button { margin-top: 12px; width: 100%; padding: 12px; font-size: 16px; background: #0369a1;
|
||
color: #fff; border: none; border-radius: 8px; cursor: pointer; }
|
||
button:hover { background: #0284c7; }
|
||
button:disabled { background: #1e3a5f; color: #475569; cursor: default; }
|
||
.result { margin-top: 20px; padding: 14px; border-radius: 8px; font-size: 22px; text-align: center; display: none; }
|
||
.ok { background: #064e3b; color: #6ee7b7; display: block; }
|
||
.err { background: #450a0a; color: #fca5a5; font-size: 14px; display: block; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="box">
|
||
<h2>Калькулятор</h2>
|
||
<div class="sub">Python 3.11 · runtime: sless</div>
|
||
<input id="expr" autofocus placeholder="например: 2 + 2 * (3 - 1)">
|
||
<button id="btn" onclick="calc()">Вычислить</button>
|
||
<div id="result" class="result"></div>
|
||
</div>
|
||
<script>
|
||
document.getElementById('expr').addEventListener('keydown', function(e) {
|
||
if (e.key === 'Enter') calc();
|
||
});
|
||
async function calc() {
|
||
const expr = document.getElementById('expr').value.trim();
|
||
if (!expr) return;
|
||
const btn = document.getElementById('btn');
|
||
const res = document.getElementById('result');
|
||
btn.disabled = true;
|
||
btn.textContent = '…';
|
||
try {
|
||
const r = await fetch('', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({expr: expr})
|
||
});
|
||
const data = await r.json();
|
||
if (data.error) {
|
||
res.className = 'result err';
|
||
res.textContent = data.error;
|
||
} else {
|
||
res.className = 'result ok';
|
||
res.textContent = expr + ' = ' + data.result;
|
||
}
|
||
} catch(e) {
|
||
res.className = 'result err';
|
||
res.textContent = 'Ошибка сети: ' + e.message;
|
||
}
|
||
btn.disabled = false;
|
||
btn.textContent = 'Вычислить';
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>"""
|
||
|
||
# Разрешённые math-функции в eval — без __builtins__ нет доступа к exec/open/etc.
|
||
_MATH_LOCALS = {k: getattr(math, k) for k in dir(math) if not k.startswith('_')}
|
||
|
||
|
||
def handler(event):
|
||
if event.get('_method') == 'POST':
|
||
expr = str(event.get('expr', '')).strip()
|
||
return _compute(expr)
|
||
# GET → HTML страница
|
||
return _PAGE
|
||
|
||
|
||
def _compute(expr):
|
||
if not expr:
|
||
return {'error': 'Введите выражение'}
|
||
try:
|
||
result = eval(expr, {'__builtins__': None}, _MATH_LOCALS) # noqa: S307
|
||
if not isinstance(result, (int, float)):
|
||
return {'error': 'Результат не является числом'}
|
||
return {'expr': expr, 'result': result}
|
||
except ZeroDivisionError:
|
||
return {'error': 'Деление на ноль'}
|
||
except Exception as exc:
|
||
return {'error': f'Ошибка: {exc}'}
|
||
|
||
|
||
def _esc(s):
|
||
# Экранируем HTML-спецсимволы — безопасный вывод в атрибут и тело.
|
||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|