- Home »

Как создать калькулятор на Python 3
Знаете, калькулятор — это та вещь, которая сидит в каждом из нас с детства. Но когда ты работаешь с серверами, автоматизацией и скриптами, простой калькулятор может превратиться в мощный инструмент для вычислений, мониторинга ресурсов и решения повседневных задач. Сегодня разберём, как создать собственный калькулятор на Python 3, который станет не просто учебным проектом, а полноценным помпомощником в ваших серверных делах. Это отличный способ освежить знания Python и заодно получить в арсенал полезную утилиту.
Зачем нужен собственный калькулятор?
Казалось бы, зачем велосипед, если есть встроенный калькулятор системы? Но если вы администрируете серверы, то наверняка сталкивались с ситуациями, когда нужно быстро посчитать:
- Объём дискового пространства в разных единицах измерения
- Пропускную способность сети
- Время выполнения backup’ов
- Стоимость ресурсов на основе метрик
- Нагрузку на процессор в процентах
Собственный калькулятор можно легко интегрировать в bash-скрипты, использовать для обработки логов или сделать веб-интерфейс для команды. Плюс это отличная база для более сложных вычислительных инструментов.
Как это работает: архитектура простого калькулятора
Калькулятор состоит из нескольких ключевых компонентов:
- Парсер выражений — разбирает строку с математическим выражением
- Вычислитель — выполняет математические операции
- Интерфейс — консольный, GUI или веб
- Обработчик ошибок — ловит некорректные выражения
Python отлично подходит для этой задачи благодаря встроенным функциям eval() (хотя с ней надо быть осторожным) и богатым библиотекам для работы с выражениями.
Пошаговая настройка: от простого к сложному
Шаг 1: Базовый консольный калькулятор
Начнём с самого простого варианта:
#!/usr/bin/env python3
# simple_calculator.py
import re
import math
def safe_eval(expression):
"""Безопасная оценка математических выражений"""
# Разрешённые символы и функции
allowed_names = {
k: v for k, v in math.__dict__.items()
if not k.startswith("__")
}
# Добавляем базовые операции
allowed_names.update({
'abs': abs,
'round': round,
'min': min,
'max': max,
})
# Проверяем на подозрительные конструкции
if re.search(r'[a-zA-Z_][a-zA-Z0-9_]*\s*\(', expression):
for match in re.finditer(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', expression):
if match.group(1) not in allowed_names:
raise ValueError(f"Функция '{match.group(1)}' не разрешена")
# Удаляем потенциально опасные конструкции
dangerous_patterns = [
r'__.*__',
r'import',
r'exec',
r'eval',
r'open',
r'file',
r'input',
r'raw_input'
]
for pattern in dangerous_patterns:
if re.search(pattern, expression):
raise ValueError(f"Обнаружена потенциально опасная конструкция")
# Оцениваем выражение
return eval(expression, {"__builtins__": {}}, allowed_names)
def main():
print("=== Серверный калькулятор ===")
print("Введите 'exit' для выхода")
print("Доступные функции: sin, cos, tan, log, sqrt, pow, и другие из math")
while True:
try:
expression = input(">>> ").strip()
if expression.lower() in ['exit', 'quit', 'q']:
print("До свидания!")
break
if not expression:
continue
result = safe_eval(expression)
print(f"Результат: {result}")
except (ValueError, SyntaxError, ZeroDivisionError) as e:
print(f"Ошибка: {e}")
except KeyboardInterrupt:
print("\nВыход...")
break
except Exception as e:
print(f"Неожиданная ошибка: {e}")
if __name__ == "__main__":
main()
Шаг 2: Добавляем специальные функции для серверов
Теперь расширим функциональность для практических задач:
#!/usr/bin/env python3
# server_calculator.py
import re
import math
import datetime
class ServerCalculator:
def __init__(self):
self.history = []
self.variables = {}
def bytes_to_human(self, bytes_value):
"""Конвертация байтов в человекочитаемый формат"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']:
if bytes_value < 1024.0:
return f"{bytes_value:.2f} {unit}"
bytes_value /= 1024.0
return f"{bytes_value:.2f} EB"
def human_to_bytes(self, human_value):
"""Конвертация из человекочитаемого формата в байты"""
units = {
'B': 1,
'KB': 1024,
'MB': 1024**2,
'GB': 1024**3,
'TB': 1024**4,
'PB': 1024**5
}
match = re.match(r'([0-9.]+)\s*([A-Z]+)', human_value.upper())
if match:
value, unit = match.groups()
return float(value) * units.get(unit, 1)
return float(human_value)
def bandwidth_time(self, size_bytes, bandwidth_mbps):
"""Расчёт времени передачи данных"""
bandwidth_bytes_per_sec = bandwidth_mbps * 1024 * 1024 / 8
seconds = size_bytes / bandwidth_bytes_per_sec
return str(datetime.timedelta(seconds=int(seconds)))
def disk_usage_percent(self, used_gb, total_gb):
"""Процент использования диска"""
return (used_gb / total_gb) * 100
def get_allowed_names(self):
"""Возвращает разрешённые имена для eval"""
allowed = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}
allowed.update({
'abs': abs,
'round': round,
'min': min,
'max': max,
'bytes_to_human': self.bytes_to_human,
'human_to_bytes': self.human_to_bytes,
'bandwidth_time': self.bandwidth_time,
'disk_usage_percent': self.disk_usage_percent,
})
allowed.update(self.variables)
return allowed
def safe_eval(self, expression):
"""Безопасная оценка выражений"""
# Обработка присваивания переменных
if '=' in expression and not any(op in expression for op in ['==', '!=', '<=', '>=']):
var_name, var_value = expression.split('=', 1)
var_name = var_name.strip()
var_value = var_value.strip()
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_name):
raise ValueError("Некорректное имя переменной")
self.variables[var_name] = self.safe_eval(var_value)
return self.variables[var_name]
# Проверка безопасности
dangerous_patterns = [
r'__.*__', r'import', r'exec', r'eval', r'open', r'file'
]
for pattern in dangerous_patterns:
if re.search(pattern, expression):
raise ValueError("Потенциально опасная конструкция")
allowed_names = self.get_allowed_names()
result = eval(expression, {"__builtins__": {}}, allowed_names)
# Сохраняем в историю
self.history.append((expression, result))
if len(self.history) > 100: # Ограничиваем историю
self.history.pop(0)
return result
def show_help(self):
"""Показать справку"""
help_text = """
=== Справка по серверному калькулятору ===
Базовые операции: +, -, *, /, **, %, //
Функции: sin, cos, tan, log, sqrt, pow, abs, round, min, max
Серверные функции:
- bytes_to_human(bytes) - конвертация байтов в читаемый формат
- human_to_bytes('1GB') - конвертация из читаемого формата в байты
- bandwidth_time(size_bytes, bandwidth_mbps) - время передачи данных
- disk_usage_percent(used_gb, total_gb) - процент использования диска
Переменные: var_name = expression
История: history (показать последние 10 операций)
Выход: exit, quit, q
"""
print(help_text)
def show_history(self):
"""Показать историю вычислений"""
if not self.history:
print("История пуста")
return
print("=== Последние 10 операций ===")
for expr, result in self.history[-10:]:
print(f"{expr} = {result}")
def run(self):
"""Основной цикл калькулятора"""
print("=== Серверный калькулятор v2.0 ===")
print("Введите 'help' для справки, 'exit' для выхода")
while True:
try:
expression = input("calc>>> ").strip()
if expression.lower() in ['exit', 'quit', 'q']:
print("До свидания!")
break
elif expression.lower() == 'help':
self.show_help()
continue
elif expression.lower() == 'history':
self.show_history()
continue
elif not expression:
continue
result = self.safe_eval(expression)
print(f"Результат: {result}")
except (ValueError, SyntaxError, ZeroDivisionError) as e:
print(f"Ошибка: {e}")
except KeyboardInterrupt:
print("\nВыход...")
break
except Exception as e:
print(f"Неожиданная ошибка: {e}")
if __name__ == "__main__":
calc = ServerCalculator()
calc.run()
Шаг 3: Веб-интерфейс с Flask
Для использования на сервере удобно иметь веб-интерфейс:
#!/usr/bin/env python3
# web_calculator.py
from flask import Flask, render_template, request, jsonify
import json
import re
import math
from server_calculator import ServerCalculator
app = Flask(__name__)
calc = ServerCalculator()
@app.route('/')
def index():
return render_template('calculator.html')
@app.route('/calculate', methods=['POST'])
def calculate():
try:
data = request.get_json()
expression = data.get('expression', '')
if not expression:
return jsonify({'error': 'Пустое выражение'})
result = calc.safe_eval(expression)
return jsonify({
'result': str(result),
'success': True
})
except Exception as e:
return jsonify({
'error': str(e),
'success': False
})
@app.route('/history')
def get_history():
return jsonify({'history': calc.history[-20:]})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
И HTML-шаблон (templates/calculator.html):
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Серверный калькулятор</title>
<style>
body {
font-family: 'Consolas', monospace;
background-color: #1e1e1e;
color: #ffffff;
margin: 0;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.input-group {
margin: 20px 0;
}
input[type="text"] {
width: 70%;
padding: 10px;
font-size: 16px;
background-color: #2d2d2d;
border: 1px solid #555;
color: #ffffff;
font-family: 'Consolas', monospace;
}
button {
padding: 10px 20px;
font-size: 16px;
background-color: #007acc;
color: white;
border: none;
cursor: pointer;
margin-left: 10px;
}
button:hover {
background-color: #005a9e;
}
.result {
margin: 20px 0;
padding: 15px;
background-color: #2d2d2d;
border-left: 4px solid #007acc;
min-height: 20px;
}
.error {
border-left-color: #ff4444;
color: #ff4444;
}
.history {
margin-top: 30px;
padding: 15px;
background-color: #2d2d2d;
border-radius: 5px;
}
.history-item {
padding: 5px 0;
border-bottom: 1px solid #444;
}
</style>
</head>
<body>
<div class="container">
<h1>🖥️ Серверный калькулятор</h1>
<div class="input-group">
<input type="text" id="expression" placeholder="Введите выражение..." />
<button onclick="calculate()">Вычислить</button>
<button onclick="clearResult()">Очистить</button>
</div>
<div id="result" class="result"></div>
<div class="history">
<h3>История вычислений</h3>
<div id="history-list"></div>
</div>
<div class="help" style="margin-top: 30px; font-size: 14px; color: #888;">
<h3>Доступные функции:</h3>
<ul>
<li>bytes_to_human(1073741824) - конвертация байтов</li>
<li>human_to_bytes('1GB') - конвертация в байты</li>
<li>bandwidth_time(1073741824, 100) - время передачи</li>
<li>disk_usage_percent(80, 100) - процент использования</li>
</ul>
</div>
</div>
<script>
function calculate() {
const expression = document.getElementById('expression').value;
const resultDiv = document.getElementById('result');
if (!expression) {
resultDiv.innerHTML = 'Введите выражение';
resultDiv.className = 'result error';
return;
}
fetch('/calculate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({expression: expression})
})
.then(response => response.json())
.then(data => {
if (data.success) {
resultDiv.innerHTML = `${expression} = ${data.result}`;
resultDiv.className = 'result';
loadHistory();
} else {
resultDiv.innerHTML = `Ошибка: ${data.error}`;
resultDiv.className = 'result error';
}
})
.catch(error => {
resultDiv.innerHTML = `Ошибка сети: ${error}`;
resultDiv.className = 'result error';
});
}
function clearResult() {
document.getElementById('result').innerHTML = '';
document.getElementById('expression').value = '';
}
function loadHistory() {
fetch('/history')
.then(response => response.json())
.then(data => {
const historyList = document.getElementById('history-list');
historyList.innerHTML = '';
data.history.forEach(item => {
const div = document.createElement('div');
div.className = 'history-item';
div.innerHTML = `${item[0]} = ${item[1]}`;
historyList.appendChild(div);
});
});
}
// Обработка Enter
document.getElementById('expression').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
calculate();
}
});
// Загрузка истории при загрузке страницы
loadHistory();
</script>
</body>
</html>
Сравнение подходов
Подход | Преимущества | Недостатки | Использование |
---|---|---|---|
eval() с ограничениями | Быстрая реализация, поддержка сложных выражений | Потенциальные уязвимости безопасности | Локальные скрипты, доверенная среда |
Парсер выражений (pyparsing) | Полный контроль, безопасность | Сложность реализации | Продакшн-приложения |
AST (Abstract Syntax Tree) | Безопасность, гибкость | Требует понимания AST | Серьёзные проекты |
Готовые библиотеки (simpleeval) | Готовое решение, безопасность | Зависимость от внешних пакетов | Коммерческие проекты |
Интеграция с системными инструментами
Теперь самое интересное — как использовать калькулятор в реальной работе:
#!/bin/bash
# system_monitor.sh
# Мониторинг дискового пространства с расчётами
df -h | while read line; do
if [[ $line =~ ^/dev ]]; then
used=$(echo $line | awk '{print $3}')
total=$(echo $line | awk '{print $2}')
mount=$(echo $line | awk '{print $6}')
# Используем наш калькулятор для расчёта процента
percent=$(python3 -c "
from server_calculator import ServerCalculator
calc = ServerCalculator()
used_bytes = calc.human_to_bytes('$used')
total_bytes = calc.human_to_bytes('$total')
print(f'{(used_bytes/total_bytes)*100:.1f}%')
")
echo "Диск $mount: $used из $total ($percent)"
fi
done
Или для мониторинга сетевого трафика:
#!/usr/bin/env python3
# network_monitor.py
import time
import subprocess
from server_calculator import ServerCalculator
calc = ServerCalculator()
def get_network_stats():
"""Получить статистику сети"""
result = subprocess.run(['cat', '/proc/net/dev'], capture_output=True, text=True)
lines = result.stdout.strip().split('\n')[2:] # Пропускаем заголовки
stats = {}
for line in lines:
parts = line.split()
interface = parts[0].rstrip(':')
rx_bytes = int(parts[1])
tx_bytes = int(parts[9])
stats[interface] = {'rx': rx_bytes, 'tx': tx_bytes}
return stats
def main():
print("=== Мониторинг сети ===")
prev_stats = get_network_stats()
while True:
time.sleep(1)
current_stats = get_network_stats()
for interface in current_stats:
if interface == 'lo': # Пропускаем loopback
continue
rx_diff = current_stats[interface]['rx'] - prev_stats[interface]['rx']
tx_diff = current_stats[interface]['tx'] - prev_stats[interface]['tx']
if rx_diff > 0 or tx_diff > 0:
rx_human = calc.bytes_to_human(rx_diff)
tx_human = calc.bytes_to_human(tx_diff)
print(f"{interface}: RX {rx_human}/s, TX {tx_human}/s")
prev_stats = current_stats
if __name__ == "__main__":
main()
Развёртывание на сервере
Для развёртывания веб-версии калькулятора на сервере создайте systemd-сервис:
# /etc/systemd/system/server-calculator.service
[Unit]
Description=Server Calculator Web Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/server-calculator
ExecStart=/usr/bin/python3 web_calculator.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Активация сервиса:
sudo systemctl daemon-reload
sudo systemctl enable server-calculator
sudo systemctl start server-calculator
sudo systemctl status server-calculator
Для production-среды рекомендую использовать gunicorn:
# requirements.txt
Flask==2.3.3
gunicorn==21.2.0
# Установка
pip3 install -r requirements.txt
# Запуск с gunicorn
gunicorn --bind 0.0.0.0:5000 --workers 4 web_calculator:app
Расширенные возможности и автоматизация
Калькулятор можно использовать для автоматизации множества задач:
- Расчёт стоимости ресурсов — интегрируйте с API облачных провайдеров
- Планирование capacity — расчёт роста дискового пространства
- Мониторинг SLA — расчёт uptime и доступности
- Оптимизация backup — расчёт времени и объёмов
- Анализ логов — статистика по размерам файлов
Если вы работаете с большими объёмами данных или нужны серьёзные вычислительные мощности, рекомендую рассмотреть аренду VPS или выделенного сервера для развёртывания более сложных вычислительных кластеров.
Альтернативные решения
Если собственный калькулятор кажется избыточным, есть готовые решения:
- bc — классический консольный калькулятор Unix
- python -c — быстрые вычисления в одну строку
- GNU Octave — для сложных математических вычислений
- R — статистические вычисления
- Jupyter Notebook — интерактивная среда
Но собственное решение даёт полный контроль и возможность интеграции с существующими системами.
Безопасность и лучшие практики
При работе с eval() всегда помните о безопасности:
- Используйте whitelist разрешённых функций
- Валидируйте входные данные
- Ограничивайте права пользователя
- Логируйте все операции
- Используйте sandbox для изоляции
Для продакшена лучше использовать библиотеки типа simpleeval или писать собственный парсер.
Интересные факты и нестандартные применения
Знаете ли вы, что калькулятор может стать основой для:
- Конфигурационных файлов — динамические значения на основе вычислений
- Шаблонизации — генерация конфигов с расчётами
- Валидации данных — проверка корректности значений
- A/B тестирования — распределение трафика по формулам
- Биллинга — расчёт стоимости услуг
Например, можно создать систему динамического scaling’а:
#!/usr/bin/env python3
# auto_scaling.py
import psutil
import time
from server_calculator import ServerCalculator
calc = ServerCalculator()
def get_system_load():
"""Получить данные о нагрузке системы"""
return {
'cpu_percent': psutil.cpu_percent(interval=1),
'memory_percent': psutil.virtual_memory().percent,
'disk_percent': psutil.disk_usage('/').percent
}
def calculate_scaling_factor():
"""Рассчитать коэффициент масштабирования"""
load = get_system_load()
# Формула для расчёта необходимости масштабирования
expression = f"({load['cpu_percent']} * 0.4) + ({load['memory_percent']} * 0.3) + ({load['disk_percent']} * 0.3)"
factor = calc.safe_eval(expression)
if factor > 80:
return "scale_up"
elif factor < 30:
return "scale_down"
else:
return "maintain"
# Основной цикл мониторинга
while True:
decision = calculate_scaling_factor()
load = get_system_load()
print(f"CPU: {load['cpu_percent']}%, RAM: {load['memory_percent']}%, Disk: {load['disk_percent']}%")
print(f"Решение: {decision}")
print("-" * 50)
time.sleep(60)
Заключение и рекомендации
Создание собственного калькулятора на Python — это не просто учебная задача, а практический инструмент для системного администратора. Он поможет вам:
- Быстро выполнять специфические расчёты
- Автоматизировать рутинные вычисления
- Интегрировать математические операции в скрипты
- Создавать удобные веб-интерфейсы для команды
- Обрабатывать данные мониторинга
Начните с простой консольной версии, постепенно добавляя функциональность. Для серверной среды веб-интерфейс будет более удобным — его можно открыть с любого устройства в сети.
Помните о безопасности при использовании eval() и всегда валидируйте входные данные. Если планируете использовать калькулятор в продакшене, рассмотрите более безопасные альтернативы парсинга выражений.
Такой инструмент станет отличным дополнением к вашему арсеналу утилит для администрирования серверов и поможет в повседневной работе с инфраструктурой.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.