Примеры кода на Python
Калькулятор на Python: пошаговое руководство от простого к сложному
Калькулятор на Python — один из лучших проектов для начинающих программистов. Он позволяет на практике освоить ключевые концепции языка: переменные, условия, циклы, функции, обработку ошибок, ООП и даже графические интерфейсы. В этом руководстве мы пройдём путь от самого простого калькулятора в несколько строк до полноценного GUI-приложения с кнопками
Каждый пример — это полностью рабочий код, который можно скопировать и запустить. После каждого блока кода идёт подробный разбор: что делает каждая строка и почему она написана именно так. Статья рассчитана на тех, кто только начинает изучать Python, но последние разделы будут полезны и более опытным разработчикам.
Что вам понадобится: Python 3.10 или новее (для примера с match/case; остальные примеры работают на Python 3.6+). Установить Python можно с официального сайта python.org
Простой калькулятор на Python (if/elif)
Начнём с самого базового варианта. Этот калькулятор на питоне запрашивает два числа и операцию, а затем выводит результат. Никаких циклов, никаких функций — чистая линейная логика
# Простой калькулятор на Python
# Запрашиваем первое число
num1 = float(input("Введите первое число: "))
# Запрашиваем операцию
operation = input("Введите операцию (+, -, *, /): ")
# Запрашиваем второе число
num2 = float(input("Введите второе число: "))
# Выполняем вычисление в зависимости от операции
if operation == "+":
result = num1 + num2
print(f"{num1} + {num2} = {result}")
elif operation == "-":
result = num1 - num2
print(f"{num1} - {num2} = {result}")
elif operation == "*":
result = num1 * num2
print(f"{num1} * {num2} = {result}")
elif operation == "/":
if num2 == 0:
print("Ошибка: деление на ноль!")
else:
result = num1 / num2
print(f"{num1} / {num2} = {result}")
else:
print(f"Неизвестная операция: {operation}")
Разбор кода калькулятора на Python строка за строкой:
float(input(...))— функцияinput()считывает ввод пользователя как строку, аfloat()преобразует эту строку в число с плавающей точкой. Мы используемfloat, а неint, чтобы можно было вводить дробные числа вроде3.14operation = input(...)— здесь мы считываем операцию как строку- Преобразовывать её не нужно, потому что
+,-,*,/— это именно строки - Конструкция
if/elif/else— это цепочка условий. Python проверяет их по порядку сверху вниз. Как только одно условие оказывается истинным, выполняется соответствующий блок, а остальные пропускаются. f"{num1} + {num2} = {result}"— это f-строка (formatted string literal). Выражения в фигурных скобках{}заменяются значениями переменных. Это удобный и читаемый способ форматирования вывода- Внутри блока деления мы отдельно проверяем
num2 == 0, чтобы программа не упала с ошибкойZeroDivisionError - Блок
elseв конце обрабатывает случай, когда пользователь ввёл что-то непредусмотренное — например,%или букву
Этот код калькулятора на Python максимально прост, но у него есть существенный недостаток: после одного вычисления программа завершается. Чтобы посчитать снова, её нужно перезапускать. Исправим это в следующем примере
Калькулятор с циклом while
Добавим бесконечный цикл while True, чтобы калькулятор на питоне работал до тех пор, пока пользователь сам не решит выйти. Это делает программу гораздо удобнее в использовании
# Калькулятор на Python с циклом while
print("=== Калькулятор ===")
print("Для выхода введите 'выход' или 'q'\n")
while True:
# Запрашиваем первое число (или команду выхода)
user_input = input("Введите первое число: ")
# Проверяем, хочет ли пользователь выйти
if user_input.lower() in ("выход", "q", "quit", "exit"):
print("До свидания!")
break
# Преобразуем ввод в число
num1 = float(user_input)
# Запрашиваем операцию
operation = input("Введите операцию (+, -, *, /): ")
# Запрашиваем второе число
num2 = float(input("Введите второе число: "))
# Выполняем вычисление
if operation == "+":
result = num1 + num2
elif operation == "-":
result = num1 - num2
elif operation == "*":
result = num1 * num2
elif operation == "/":
if num2 == 0:
print("Ошибка: деление на ноль!\n")
continue
result = num1 / num2
else:
print(f"Неизвестная операция '{operation}'. Попробуйте снова.\n")
continue
print(f"Результат: {num1} {operation} {num2} = {result}\n")
Что нового в этом примере:
while True— создаёт бесконечный цикл. Программа будет выполняться снова и снова, пока мы не прервём цикл явно.break— немедленно прерывает циклwhile. Мы вызываем его, когда пользователь вводит «выход» или «q».continue— пропускает оставшуюся часть текущей итерации цикла и начинает новую. Мы используем его при ошибках (деление на ноль, неизвестная операция), чтобы калькулятор не пытался вывести несуществующий результат, а просто предложил ввести данные заново.user_input.lower()— методlower()приводит строку к нижнему регистру. Это нужно, чтобы команда выхода работала независимо от того, написал пользователь «Выход», «ВЫХОД» или «выход».- Проверка
in ("выход", "q", "quit", "exit")— операторinпроверяет, входит ли значение в кортеж допустимых вариантов. Так мы поддерживаем несколько команд выхода одновременно.
Программа стала удобнее, но код по-прежнему довольно «плоский». Вся логика находится в одном месте, и если мы захотим добавить новые операции, файл быстро станет нечитаемым. Пора разделить код на функции
Калькулятор с функциями
Разделение кода на функции — одна из важнейших практик в программировании. Принцип DRY (Don’t Repeat Yourself — «не повторяй себя») говорит: если какой-то фрагмент кода используется несколько раз, вынесите его в функцию. Это делает как сделать калькулятор в Python более чистым и поддерживаемым
# Калькулятор на Python с функциями
def add(a: float, b: float) -> float:
"""Сложение двух чисел."""
return a + b
def subtract(a: float, b: float) -> float:
"""Вычитание двух чисел."""
return a - b
def multiply(a: float, b: float) -> float:
"""Умножение двух чисел."""
return a * b
def divide(a: float, b: float) -> float | None:
"""Деление двух чисел. Возвращает None при делении на ноль."""
if b == 0:
return None
return a / b
def get_number(prompt: str) -> float | None:
"""Запрашивает число у пользователя. Возвращает None при ошибке ввода."""
try:
return float(input(prompt))
except ValueError:
return None
def calculate(num1: float, operation: str, num2: float) -> tuple[float | None, str]:
"""
Выполняет вычисление и возвращает кортеж (результат, сообщение).
Если произошла ошибка, результат будет None.
"""
operations = {
"+": add,
"-": subtract,
"*": multiply,
"/": divide,
}
if operation not in operations:
return None, f"Неизвестная операция: '{operation}'"
result = operations[operation](num1, num2)
if result is None and operation == "/":
return None, "Ошибка: деление на ноль!"
return result, f"{num1} {operation} {num2} = {result}"
def main():
"""Главная функция калькулятора."""
print("=== Калькулятор на Python ===")
print("Операции: +, -, *, /")
print("Для выхода введите 'q'\n")
while True:
# Получаем первое число
user_input = input("Первое число (или 'q' для выхода): ")
if user_input.lower() == "q":
print("До свидания!")
break
try:
num1 = float(user_input)
except ValueError:
print("Это не число. Попробуйте снова.\n")
continue
# Получаем операцию
operation = input("Операция (+, -, *, /): ").strip()
# Получаем второе число
num2 = get_number("Второе число: ")
if num2 is None:
print("Это не число. Попробуйте снова.\n")
continue
# Вычисляем и выводим результат
result, message = calculate(num1, operation, num2)
print(f"{message}\n")
# Точка входа в программу
if __name__ == "__main__":
main()
Разбор ключевых изменений:
- Отдельные функции для каждой операции (
add,subtract,multiply,divide). Каждая делает ровно одну вещь. Это упрощает тестирование и отладку. - Словарь
operations— вместо длинной цепочкиif/elifмы используем словарь, где ключ — символ операции, а значение — ссылка на функцию. Записьoperations[operation](num1, num2)вызывает нужную функцию по символу операции. Это элегантный паттерн, который часто используется в Python. - Функция
get_number— инкапсулирует логику получения числа от пользователя с обработкой ошибки. Если пользователь введёт текст вместо числа, функция вернётNone, а не вызовет исключение. - Функция
calculate— принимает два числа и операцию, возвращает кортеж из результата и текстового сообщения. Это разделяет логику вычисления и логику ввода-вывода. if __name__ == "__main__"— стандартная конструкция Python. Код внутри этого блока выполняется только при прямом запуске файла, но не при его импорте как модуля. Это хорошая практика для любого скрипта.- Аннотации типов (
a: float,-> float | None) — не влияют на работу программы, но служат документацией и помогают IDE подсвечивать ошибки. Синтаксисfloat | Noneдоступен начиная с Python 3.10
Калькулятор с match/case (Python 3.10+)
В Python 3.10 появился оператор структурного сопоставления match/case — аналог switch из других языков, но значительно мощнее. Давайте перепишем калькулятор питон с его использованием
# Калькулятор на Python с match/case (требует Python 3.10+)
import sys
def calculate(num1: float, operation: str, num2: float) -> str:
"""Выполняет вычисление и возвращает строку с результатом."""
match operation:
case "+":
result = num1 + num2
case "-":
result = num1 - num2
case "*":
result = num1 * num2
case "/":
if num2 == 0:
return "Ошибка: деление на ноль!"
result = num1 / num2
case "**" | "^":
result = num1 ** num2
case "%":
if num2 == 0:
return "Ошибка: деление на ноль при взятии остатка!"
result = num1 % num2
case "//":
if num2 == 0:
return "Ошибка: деление на ноль!"
result = num1 // num2
case _:
return f"Неизвестная операция: '{operation}'"
return f"{num1} {operation} {num2} = {result}"
def show_help():
"""Выводит справку по доступным операциям."""
print("""
Доступные операции:
+ — сложение
- — вычитание
* — умножение
/ — деление
** — возведение в степень (также работает ^)
% — остаток от деления
// — целочисленное деление
Команды:
help — показать эту справку
q — выход из программы
""")
def main():
"""Главная функция калькулятора."""
# Проверяем версию Python
if sys.version_info < (3, 10):
print("Этот калькулятор требует Python 3.10 или новее!")
print(f"У вас: Python {sys.version_info.major}.{sys.version_info.minor}")
sys.exit(1)
print("=== Калькулятор (Python 3.10+) ===")
print("Введите 'help' для справки, 'q' для выхода\n")
while True:
user_input = input(">>> ").strip()
# Обрабатываем команды
match user_input.lower():
case "q" | "quit" | "exit" | "выход":
print("До свидания!")
break
case "help" | "помощь" | "?":
show_help()
continue
case "":
continue
# Пытаемся разобрать выражение: число операция число
parts = user_input.split()
if len(parts) != 3:
print("Формат ввода: число операция число")
print("Пример: 10 + 5\n")
continue
try:
num1 = float(parts[0])
operation = parts[1]
num2 = float(parts[2])
except ValueError:
print("Ошибка: проверьте, что первое и третье значения — числа.\n")
continue
print(f"{calculate(num1, operation, num2)}\n")
if __name__ == "__main__":
main()
Что нового в этом примере:
match/case— основа этого примера. Операторmatchсравнивает значение переменнойoperationс паттернами, указанными послеcase. В отличие отswitchв C или Java, здесь не нуженbreak— выполняется только один блок.case "**" | "^"— символ|внутриcaseозначает «ИЛИ». Этот паттерн сработает, если пользователь ввёл**или^. Удобно для поддержки нескольких вариантов одной операции.case _— подчёркивание является шаблоном-подстановкой (wildcard), который срабатывает, если ни один из предыдущих паттернов не подошёл. Это аналогelseвif/elif/elseилиdefaultвswitch.- Новые операции — мы добавили возведение в степень (
**), остаток от деления (%) и целочисленное деление (//). - Ввод в одну строку — теперь пользователь пишет
10 + 5, а не вводит каждое значение отдельно. Методsplit()разбивает строку по пробелам на список. sys.version_info— проверяем версию Python при запуске, чтобы дать понятное сообщение об ошибке вместо невнятногоSyntaxError
Калькулятор с обработкой ошибок
Обработка ошибок — критически важная часть любой программы. В предыдущих примерах мы уже использовали try/except фрагментарно. Теперь создадим калькулятор на Python с полноценной защитой от всех типичных ошибок пользователя
# Калькулятор на Python с полной обработкой ошибок
class CalculatorError(Exception):
"""Базовое исключение для ошибок калькулятора."""
pass
class InvalidOperationError(CalculatorError):
"""Ошибка: неизвестная операция."""
pass
class DivisionByZeroError(CalculatorError):
"""Ошибка: деление на ноль."""
pass
class InvalidInputError(CalculatorError):
"""Ошибка: некорректный ввод."""
pass
# Словарь поддерживаемых операций
OPERATIONS: dict[str, str] = {
"+": "сложение",
"-": "вычитание",
"*": "умножение",
"/": "деление",
"**": "возведение в степень",
"%": "остаток от деления",
}
def parse_number(value: str) -> float:
"""
Преобразует строку в число.
Поддерживает запятую как десятичный разделитель.
"""
# Заменяем запятую на точку (частая ошибка русскоязычных пользователей)
value = value.strip().replace(",", ".")
if not value:
raise InvalidInputError("Пустой ввод. Введите число.")
try:
number = float(value)
except ValueError:
raise InvalidInputError(
f"'{value}' — это не число. "
f"Допустимы цифры, точка и знак минуса."
)
# Проверяем на специальные значения float
if number != number: # проверка на NaN
raise InvalidInputError("Значение NaN не поддерживается.")
if number == float("inf") or number == float("-inf"):
raise InvalidInputError("Бесконечность не поддерживается.")
return number
def safe_calculate(num1: float, operation: str, num2: float) -> float:
"""
Выполняет безопасное вычисление.
Выбрасывает CalculatorError при проблемах.
"""
if operation not in OPERATIONS:
available = ", ".join(OPERATIONS.keys())
raise InvalidOperationError(
f"Операция '{operation}' не поддерживается.\n"
f" Доступные операции: {available}"
)
# Проверки деления на ноль
if operation in ("/", "%") and num2 == 0:
raise DivisionByZeroError(
"Деление на ноль невозможно. "
"Второе число должно быть отличным от нуля."
)
# Защита от слишком больших степеней
if operation == "**":
if abs(num2) > 1000:
raise CalculatorError(
"Слишком большая степень (максимум: 1000). "
"Вычисление может занять очень много времени."
)
# Выполняем вычисление
match operation:
case "+":
return num1 + num2
case "-":
return num1 - num2
case "*":
return num1 * num2
case "/":
return num1 / num2
case "**":
return num1 ** num2
case "%":
return num1 % num2
def format_result(value: float) -> str:
"""Форматирует результат: убирает лишние нули у целых чисел."""
if value == int(value) and abs(value) < 1e15:
return str(int(value))
return f"{value:.10g}"
def main():
"""Главная функция калькулятора с обработкой ошибок."""
print("=== Калькулятор с обработкой ошибок ===")
print("Формат: число операция число (например: 10 + 5)")
print("Для выхода введите 'q'\n")
while True:
try:
user_input = input(">>> ").strip()
if user_input.lower() in ("q", "quit", "выход"):
print("До свидания!")
break
if not user_input:
continue
# Разбираем ввод
parts = user_input.split()
if len(parts) != 3:
raise InvalidInputError(
"Нужно ввести ровно три значения: число, операцию и число.\n"
" Пример: 10 + 5"
)
# Парсим числа и операцию
num1 = parse_number(parts[0])
operation = parts[1]
num2 = parse_number(parts[2])
# Вычисляем
result = safe_calculate(num1, operation, num2)
formatted = format_result(result)
print(f" = {formatted}\n")
except CalculatorError as e:
# Наши ожидаемые ошибки — выводим понятное сообщение
print(f" ⚠ {e}\n")
except KeyboardInterrupt:
# Пользователь нажал Ctrl+C
print("\n\nПрервано пользователем. До свидания!")
break
except EOFError:
# Конец ввода (например, перенаправление из файла)
print("\nКонец ввода. До свидания!")
break
if __name__ == "__main__":
main()
Подробный разбор обработки ошибок:
- Собственная иерархия исключений. Мы создали базовый класс
CalculatorErrorи три наследника. Это позволяет перехватывать все ошибки калькулятора одним блокомexcept CalculatorError, но при необходимости обрабатывать каждый тип отдельно. parse_number— надёжная функция для преобразования ввода в число. Она обрабатывает: пустой ввод, текст вместо числа, запятую вместо точки (очень частая ошибка), а также специальные значенияNaNи бесконечность.- Замена запятой на точку (
value.replace(",", ".")) — в России принято использовать запятую как десятичный разделитель. Наш калькулятор это учитывает: ввод3,14будет корректно преобразован в3.14. - Защита от больших степеней — выражение вроде
2 ** 1000000может «подвесить» программу на долгое время. Мы ограничиваем показатель степени числом 1000. format_result— убирает лишний.0у целых результатов. Без неё10 + 5выводило бы15.0вместо15.KeyboardInterrupt— перехватывает нажатиеCtrl+C. Без этого обработчика при нажатииCtrl+Cпользователь увидит пугающий трейсбек.EOFError— возникает, когда ввод заканчивается (например, при перенаправлении из файла:echo "10 + 5" | python calculator.py)
Продвинутый калькулятор с модулем math
Стандартная библиотека Python содержит модуль math с десятками математических функций. Используем его, чтобы сделать калькулятор в Python по-настоящему мощным: с квадратным корнем, тригонометрией, логарифмами и факториалом
# Продвинутый калькулятор на Python с модулем math
import math
def show_help():
"""Выводит справку по всем доступным операциям."""
print("""
╔══════════════════════════════════════════════════════════╗
║ Продвинутый калькулятор — Справка ║
╠══════════════════════════════════════════════════════════╣
║ Бинарные операции (формат: число операция число): ║
║ + сложение - вычитание ║
║ * умножение / деление ║
║ ** степень % остаток от деления ║
║ // целочисленное деление ║
║ ║
║ Унарные операции (формат: операция число): ║
║ sqrt квадратный корень abs модуль числа ║
║ sin синус (градусы) cos косинус (градусы) ║
║ tan тангенс (градусы) log логарифм (ln) ║
║ log10 десятичный логарифм ║
║ fact факториал ceil округление вверх ║
║ floor округление вниз round округление ║
║ ║
║ Константы: ║
║ pi число Пи e число Эйлера ║
║ ║
║ Команды: help — справка, q — выход ║
╚══════════════════════════════════════════════════════════╝
""")
def unary_operation(operation: str, value: float) -> float:
"""Выполняет унарную операцию над одним числом."""
match operation:
case "sqrt":
if value < 0:
raise ValueError("Квадратный корень из отрицательного числа не определён.")
return math.sqrt(value)
case "abs":
return abs(value)
case "sin":
return math.sin(math.radians(value))
case "cos":
return math.cos(math.radians(value))
case "tan":
return math.tan(math.radians(value))
case "log":
if value <= 0:
raise ValueError("Логарифм определён только для положительных чисел.")
return math.log(value)
case "log10":
if value <= 0:
raise ValueError("Логарифм определён только для положительных чисел.")
return math.log10(value)
case "fact":
if value < 0 or value != int(value):
raise ValueError("Факториал определён только для неотрицательных целых чисел.")
if value > 170:
raise ValueError("Факториал для чисел больше 170 слишком велик.")
return float(math.factorial(int(value)))
case "ceil":
return float(math.ceil(value))
case "floor":
return float(math.floor(value))
case "round":
return float(round(value))
case _:
raise ValueError(f"Неизвестная операция: '{operation}'")
def binary_operation(num1: float, op: str, num2: float) -> float:
"""Выполняет бинарную операцию над двумя числами."""
match op:
case "+":
return num1 + num2
case "-":
return num1 - num2
case "*":
return num1 * num2
case "/":
if num2 == 0:
raise ZeroDivisionError("Деление на ноль!")
return num1 / num2
case "**":
return num1 ** num2
case "%":
if num2 == 0:
raise ZeroDivisionError("Деление на ноль при взятии остатка!")
return num1 % num2
case "//":
if num2 == 0:
raise ZeroDivisionError("Деление на ноль!")
return num1 // num2
case _:
raise ValueError(f"Неизвестная операция: '{op}'")
def format_number(value: float) -> str:
"""Форматирует число для вывода."""
if abs(value) < 1e-10:
return "0"
if value == int(value) and abs(value) < 1e15:
return str(int(value))
return f"{value:.10g}"
# Список унарных операций для определения типа ввода
UNARY_OPS = {"sqrt", "abs", "sin", "cos", "tan", "log", "log10", "fact", "ceil", "floor", "round"}
# Константы
CONSTANTS = {"pi": math.pi, "e": math.e}
def main():
"""Главная функция продвинутого калькулятора."""
print("=== Продвинутый калькулятор ===")
print("Введите 'help' для списка операций\n")
# Хранение последнего результата
last_result: float | None = None
while True:
try:
raw = input(">>> ").strip()
if not raw:
continue
command = raw.lower()
if command in ("q", "quit", "выход"):
print("До свидания!")
break
if command in ("help", "помощь", "?"):
show_help()
continue
# Проверяем, не ввели ли константу
if command in CONSTANTS:
last_result = CONSTANTS[command]
print(f" = {format_number(last_result)}\n")
continue
parts = raw.split()
# Подставляем последний результат вместо слова "ans"
for i, part in enumerate(parts):
if part.lower() == "ans":
if last_result is None:
raise ValueError("Нет предыдущего результата (ans).")
parts[i] = str(last_result)
if len(parts) == 2:
# Унарная операция: sqrt 16, sin 90, и т.д.
op = parts[0].lower()
value = float(parts[1].replace(",", "."))
result = unary_operation(op, value)
elif len(parts) == 3:
# Бинарная операция: 10 + 5
num1 = float(parts[0].replace(",", "."))
op = parts[1]
num2 = float(parts[2].replace(",", "."))
result = binary_operation(num1, op, num2)
else:
print("Формат: число операция число или операция число")
print("Пример: 10 + 5 или sqrt 16\n")
continue
last_result = result
print(f" = {format_number(result)}\n")
except (ValueError, ZeroDivisionError) as e:
print(f" Ошибка: {e}\n")
except KeyboardInterrupt:
print("\nДо свидания!")
break
if __name__ == "__main__":
main()
Ключевые возможности продвинутого калькулятора:
- Унарные и бинарные операции. Калькулятор различает два формата ввода:
sqrt 16(одно число) и10 + 5(два числа). Определяется по количеству частей послеsplit(). - Модуль
math. Используемmath.sqrtдля корня,math.sin/math.cos/math.tanдля тригонометрии,math.logиmath.log10для логарифмов,math.factorialдля факториала. - Тригонометрия в градусах. Функции
math.sinи подобные работают в радианах, но обычные пользователи привыкли к градусам. Поэтому мы преобразуем ввод черезmath.radians(). - Переменная
ans— хранит последний результат. Можно написатьans * 2, чтобы удвоить предыдущий ответ. Это как кнопкаAnsна обычном калькуляторе. - Константы
piиe— вводите простоpiилиe, чтобы увидеть их значения. Их также можно использовать в вычислениях какans
GUI-калькулятор на Tkinter
Теперь создадим графический калькулятор на питоне с настоящими кнопками. Мы используем библиотеку tkinter, которая входит в стандартную поставку Python. Важный момент: мы не используем eval() для вычисления выражений, потому что eval() может выполнить произвольный код и представляет угрозу безопасности. Вместо этого мы реализуем безопасный парсер выражений
# GUI-калькулятор на Python (Tkinter) — безопасная версия без eval()
import tkinter as tk
from tkinter import font as tkfont
class SafeCalculator:
"""
Безопасный вычислитель арифметических выражений.
Поддерживает: +, -, *, /, скобки, унарный минус.
НЕ использует eval() — разбирает выражение вручную.
"""
def __init__(self, expression: str):
# Убираем пробелы и сохраняем выражение
self.expression = expression.replace(" ", "")
self.pos = 0 # текущая позиция при разборе
def parse(self) -> float:
"""Основная точка входа — разбирает всё выражение."""
result = self._parse_addition()
if self.pos < len(self.expression):
raise ValueError(
f"Неожиданный символ: '{self.expression[self.pos]}'"
)
return result
def _parse_addition(self) -> float:
"""Разбирает сложение и вычитание (низший приоритет)."""
result = self._parse_multiplication()
while self.pos < len(self.expression) and self.expression[self.pos] in "+-":
op = self.expression[self.pos]
self.pos += 1
right = self._parse_multiplication()
if op == "+":
result += right
else:
result -= right
return result
def _parse_multiplication(self) -> float:
"""Разбирает умножение и деление (высший приоритет)."""
result = self._parse_unary()
while self.pos < len(self.expression) and self.expression[self.pos] in "*/":
op = self.expression[self.pos]
self.pos += 1
right = self._parse_unary()
if op == "*":
result *= right
else:
if right == 0:
raise ZeroDivisionError("Деление на ноль")
result /= right
return result
def _parse_unary(self) -> float:
"""Разбирает унарный минус и плюс."""
if self.pos < len(self.expression):
if self.expression[self.pos] == "-":
self.pos += 1
return -self._parse_unary()
if self.expression[self.pos] == "+":
self.pos += 1
return self._parse_unary()
return self._parse_primary()
def _parse_primary(self) -> float:
"""Разбирает числа и выражения в скобках."""
if self.pos >= len(self.expression):
raise ValueError("Неожиданный конец выражения")
# Скобки
if self.expression[self.pos] == "(":
self.pos += 1 # пропускаем '('
result = self._parse_addition()
if self.pos >= len(self.expression) or self.expression[self.pos] != ")":
raise ValueError("Не закрыта скобка")
self.pos += 1 # пропускаем ')'
return result
# Число
start = self.pos
while self.pos < len(self.expression) and (
self.expression[self.pos].isdigit() or self.expression[self.pos] == "."
):
self.pos += 1
if start == self.pos:
raise ValueError(
f"Ожидалось число, получено: '{self.expression[self.pos]}'"
)
return float(self.expression[start:self.pos])
def safe_eval(expression: str) -> str:
"""Безопасно вычисляет арифметическое выражение."""
if not expression or not expression.strip():
return ""
try:
calc = SafeCalculator(expression)
result = calc.parse()
# Форматируем результат
if result == int(result) and abs(result) < 1e15:
return str(int(result))
return f"{result:.10g}"
except ZeroDivisionError:
return "Ошибка: деление на 0"
except (ValueError, IndexError) as e:
return f"Ошибка"
class CalculatorApp:
"""Графический калькулятор на Tkinter."""
def __init__(self):
# Создаём главное окно
self.root = tk.Tk()
self.root.title("Калькулятор")
self.root.resizable(False, False)
self.root.configure(bg="#2b2b2b")
# Текущее выражение
self.expression = ""
# Настраиваем интерфейс
self._create_display()
self._create_buttons()
# Привязка клавиатуры
self.root.bind("<Key>", self._on_key_press)
self.root.bind("<Return>", lambda e: self._calculate())
self.root.bind("<BackSpace>", lambda e: self._backspace())
self.root.bind("<Escape>", lambda e: self._clear())
def _create_display(self):
"""Создаёт поле вывода (дисплей калькулятора)."""
display_frame = tk.Frame(self.root, bg="#2b2b2b", padx=10, pady=10)
display_frame.pack(fill="x")
# Строка с выражением (мелкий шрифт, сверху)
self.expr_label = tk.Label(
display_frame,
text="",
font=("Helvetica", 14),
bg="#2b2b2b",
fg="#888888",
anchor="e",
)
self.expr_label.pack(fill="x")
# Основной дисплей (крупный шрифт)
self.display_var = tk.StringVar(value="0")
self.display = tk.Label(
display_frame,
textvariable=self.display_var,
font=("Helvetica", 32, "bold"),
bg="#2b2b2b",
fg="#ffffff",
anchor="e",
)
self.display.pack(fill="x")
def _create_buttons(self):
"""Создаёт сетку кнопок."""
btn_frame = tk.Frame(self.root, bg="#2b2b2b", padx=5, pady=5)
btn_frame.pack()
# Раскладка кнопок: текст, строка, столбец, ширина столбцов, цвет
buttons = [
("C", 0, 0, 1, "#a5a5a5", "#000000"),
("()", 0, 1, 1, "#a5a5a5", "#000000"),
("%", 0, 2, 1, "#a5a5a5", "#000000"),
("/", 0, 3, 1, "#ff9f0a", "#ffffff"),
("7", 1, 0, 1, "#505050", "#ffffff"),
("8", 1, 1, 1, "#505050", "#ffffff"),
("9", 1, 2, 1, "#505050", "#ffffff"),
("*", 1, 3, 1, "#ff9f0a", "#ffffff"),
("4", 2, 0, 1, "#505050", "#ffffff"),
("5", 2, 1, 1, "#505050", "#ffffff"),
("6", 2, 2, 1, "#505050", "#ffffff"),
("-", 2, 3, 1, "#ff9f0a", "#ffffff"),
("1", 3, 0, 1, "#505050", "#ffffff"),
("2", 3, 1, 1, "#505050", "#ffffff"),
("3", 3, 2, 1, "#505050", "#ffffff"),
("+", 3, 3, 1, "#ff9f0a", "#ffffff"),
("0", 4, 0, 2, "#505050", "#ffffff"),
(".", 4, 2, 1, "#505050", "#ffffff"),
("=", 4, 3, 1, "#ff9f0a", "#ffffff"),
]
btn_font = tkfont.Font(family="Helvetica", size=18)
for text, row, col, colspan, bg_color, fg_color in buttons:
btn = tk.Button(
btn_frame,
text=text,
font=btn_font,
bg=bg_color,
fg=fg_color,
activebackground=bg_color,
activeforeground=fg_color,
border=0,
width=4 * colspan + (colspan - 1),
height=2,
command=lambda t=text: self._on_button_click(t),
)
btn.grid(row=row, column=col, columnspan=colspan, padx=2, pady=2, sticky="nsew")
# Настраиваем равномерное распределение столбцов
for i in range(4):
btn_frame.grid_columnconfigure(i, weight=1)
def _on_button_click(self, text: str):
"""Обрабатывает нажатие кнопки."""
if text == "C":
self._clear()
elif text == "=":
self._calculate()
elif text == "()":
self._insert_bracket()
else:
self._append(text)
def _append(self, char: str):
"""Добавляет символ к выражению."""
if self.expression == "0" and char.isdigit():
self.expression = char
else:
self.expression += char
self.display_var.set(self.expression)
def _clear(self):
"""Очищает выражение."""
self.expression = ""
self.display_var.set("0")
self.expr_label.config(text="")
def _backspace(self):
"""Удаляет последний символ."""
self.expression = self.expression[:-1]
self.display_var.set(self.expression if self.expression else "0")
def _insert_bracket(self):
"""Вставляет открывающую или закрывающую скобку по контексту."""
open_count = self.expression.count("(")
close_count = self.expression.count(")")
# Ставим закрывающую скобку, если есть незакрытые
# и последний символ — цифра или закрывающая скобка
if (
open_count > close_count
and self.expression
and (self.expression[-1].isdigit() or self.expression[-1] == ")")
):
self._append(")")
else:
self._append("(")
def _calculate(self):
"""Вычисляет текущее выражение."""
if not self.expression:
return
# Заменяем символы для вычисления
expr = self.expression.replace("%", "/100*")
result = safe_eval(expr)
if result:
# Показываем выражение сверху
self.expr_label.config(text=f"{self.expression} =")
# Показываем результат
self.expression = result
self.display_var.set(result)
def _on_key_press(self, event):
"""Обрабатывает ввод с клавиатуры."""
char = event.char
if char in "0123456789.+-*/()":
self._append(char)
def run(self):
"""Запускает главный цикл приложения."""
self.root.mainloop()
if __name__ == "__main__":
app = CalculatorApp()
app.run()
Подробный разбор GUI-калькулятора:
Этот пример — самый большой в статье, поэтому разберём его по частям
Класс SafeCalculator — безопасный парсер выражений.* Это рекурсивный нисходящий парсер (recursive descent parser). Он разбирает выражение, учитывая приоритет операций:
- Сначала обрабатываются скобки и числа (
_parse_primary) - Затем унарный минус (
_parse_unary) - Потом умножение и деление (
_parse_multiplication) - В последнюю очередь — сложение и вычитание (
_parse_addition)
Такой порядок гарантирует, что 2 + 3 * 4 даст 14, а не 20.
Почему не eval()? Функция eval() выполняет произвольный Python-код. Если передать ей __import__('os').system('rm -rf /'), она попытается удалить все файлы на диске. Даже в локальном приложении это плохая привычка — лучше сразу учиться писать безопасный код.
Класс CalculatorApp — графический интерфейс:
_create_display— создаёт два текстовых поля: маленькое для выражения и большое для результата._create_buttons— строит сетку кнопок из списка кортежей. Каждый кортеж содержит текст кнопки, её позицию в сетке, размер и цвета. Параметрcommand=lambda t=text: self._on_button_click(t)привязывает нажатие кнопки к методу обработки. Обратите внимание наt=text— это фиксирует значение переменной в замыкании (без этого все кнопки вызывали бы обработчик с последним значениемtext)._insert_bracket— «умная» кнопка скобок. Если есть незакрытые скобки и предыдущий символ — цифра, вставляет). Иначе —(.- Поддержка клавиатуры — метод
_on_key_pressпозволяет набирать выражения не только кнопками, но и с клавиатуры.Enterвычисляет,Backspaceудаляет,Escapeочищает
Калькулятор как класс (ООП)
Объектно-ориентированное программирование (ООП) — мощная парадигма, которая позволяет организовать код в виде объектов со своими данными и поведением. Создадим калькулятор на Python как класс с историей вычислений, памятью и возможностью расширения
# Калькулятор на Python в стиле ООП
from dataclasses import dataclass, field
from datetime import datetime
import math
@dataclass
class HistoryEntry:
"""Запись в истории вычислений."""
expression: str
result: float
timestamp: datetime = field(default_factory=datetime.now)
def __str__(self) -> str:
time_str = self.timestamp.strftime("%H:%M:%S")
return f"[{time_str}] {self.expression} = {self.result}"
class Calculator:
"""
Калькулятор с историей вычислений и памятью.
Использование:
calc = Calculator()
calc.add(10, 5) # 15.0
calc.subtract(10, 3) # 7.0
calc.history() # показать историю
"""
def __init__(self):
self._history: list[HistoryEntry] = []
self._memory: float = 0.0
self._last_result: float | None = None
# === Основные операции ===
def add(self, a: float, b: float) -> float:
"""Сложение."""
return self._record(f"{a} + {b}", a + b)
def subtract(self, a: float, b: float) -> float:
"""Вычитание."""
return self._record(f"{a} - {b}", a - b)
def multiply(self, a: float, b: float) -> float:
"""Умножение."""
return self._record(f"{a} * {b}", a * b)
def divide(self, a: float, b: float) -> float:
"""Деление. Выбрасывает ZeroDivisionError при делении на ноль."""
if b == 0:
raise ZeroDivisionError("Деление на ноль невозможно")
return self._record(f"{a} / {b}", a / b)
def power(self, base: float, exponent: float) -> float:
"""Возведение в степень."""
return self._record(f"{base} ** {exponent}", base ** exponent)
def sqrt(self, value: float) -> float:
"""Квадратный корень."""
if value < 0:
raise ValueError("Корень из отрицательного числа не определён")
return self._record(f"sqrt({value})", math.sqrt(value))
def modulo(self, a: float, b: float) -> float:
"""Остаток от деления."""
if b == 0:
raise ZeroDivisionError("Деление на ноль невозможно")
return self._record(f"{a} % {b}", a % b)
# === Память ===
def memory_store(self, value: float | None = None) -> None:
"""Сохраняет значение в память. Без аргумента — сохраняет последний результат."""
if value is not None:
self._memory = value
elif self._last_result is not None:
self._memory = self._last_result
else:
raise ValueError("Нет значения для сохранения в память")
print(f" Сохранено в память: {self._memory}")
def memory_recall(self) -> float:
"""Возвращает значение из памяти."""
return self._memory
def memory_clear(self) -> None:
"""Очищает память."""
self._memory = 0.0
print(" Память очищена")
def memory_add(self, value: float | None = None) -> None:
"""Прибавляет значение к памяти."""
val = value if value is not None else (self._last_result or 0)
self._memory += val
print(f" Память: {self._memory}")
# === История ===
def history(self, count: int = 10) -> list[HistoryEntry]:
"""Возвращает и печатает последние записи истории."""
entries = self._history[-count:]
if not entries:
print(" История пуста")
else:
print(f"\n Последние {len(entries)} вычислений:")
for entry in entries:
print(f" {entry}")
print()
return entries
def clear_history(self) -> None:
"""Очищает историю."""
self._history.clear()
print(" История очищена")
# === Свойства ===
@property
def last(self) -> float | None:
"""Последний результат."""
return self._last_result
@property
def memory(self) -> float:
"""Текущее значение в памяти."""
return self._memory
# === Внутренние методы ===
def _record(self, expression: str, result: float) -> float:
"""Записывает операцию в историю и возвращает результат."""
entry = HistoryEntry(expression=expression, result=result)
self._history.append(entry)
self._last_result = result
return result
def __repr__(self) -> str:
return (
f"Calculator(history={len(self._history)} entries, "
f"memory={self._memory}, last={self._last_result})"
)
def interactive_mode():
"""Интерактивный режим калькулятора."""
calc = Calculator()
print("=== ООП-калькулятор ===")
print("Команды: history, mc, mr, ms, m+, clear, q\n")
while True:
try:
raw = input(">>> ").strip()
if not raw:
continue
command = raw.lower()
# Специальные команды
if command in ("q", "quit", "выход"):
print("До свидания!")
break
elif command in ("history", "h", "история"):
calc.history()
continue
elif command == "mc":
calc.memory_clear()
continue
elif command == "mr":
val = calc.memory_recall()
print(f" Память: {val}")
continue
elif command == "ms":
calc.memory_store()
continue
elif command == "m+":
calc.memory_add()
continue
elif command in ("clear", "очистить"):
calc.clear_history()
continue
parts = raw.split()
# Унарные операции
if len(parts) == 2 and parts[0].lower() == "sqrt":
value = float(parts[1].replace(",", "."))
result = calc.sqrt(value)
print(f" = {result}\n")
continue
# Бинарные операции
if len(parts) == 3:
a = float(parts[0].replace(",", "."))
op = parts[1]
b = float(parts[2].replace(",", "."))
match op:
case "+":
result = calc.add(a, b)
case "-":
result = calc.subtract(a, b)
case "*":
result = calc.multiply(a, b)
case "/":
result = calc.divide(a, b)
case "**":
result = calc.power(a, b)
case "%":
result = calc.modulo(a, b)
case _:
print(f" Неизвестная операция: {op}\n")
continue
print(f" = {result}\n")
continue
print(" Формат: число операция число или sqrt число\n")
except (ValueError, ZeroDivisionError) as e:
print(f" Ошибка: {e}\n")
except KeyboardInterrupt:
print("\nДо свидания!")
break
if __name__ == "__main__":
interactive_mode()
Разбор ООП-калькулятора
@dataclass— декоратор, который автоматически создаёт методы__init__,__repr__и__eq__на основе аннотаций полей.HistoryEntryхранит выражение, результат и время.field(default_factory=datetime.now)— для изменяемых значений по умолчанию в dataclass используетсяdefault_factory. Без этого все записи получили бы одинаковое время.- Приватные атрибуты (
self._history,self._memory) — в Python одно подчёркивание в начале имени означает «это внутренняя деталь реализации, не трогайте напрямую». Это конвенция, а не жёсткое ограничение. @property— декоратор, превращающий метод в свойство. После этого можно писатьcalc.lastвместоcalc.last(). Свойства удобны для чтения данных без возможности их случайно изменить.- Метод
_record— все операции вызывают его для записи в историю. Это единая точка для логирования, что упрощает добавление новой функциональности (например, экспорта истории в файл). - Память калькулятора — имитирует кнопки
MS,MR,MC,M+настоящего калькулятора.memory_storeсохраняет значение,memory_recallвозвращает,memory_addприбавляет к текущему значению в памяти
Также этот калькулятор можно использовать как библиотеку в другом Python-скрипте:
from calculator_oop import Calculator
calc = Calculator()
total = calc.add(100, 50) # 150.0
tax = calc.multiply(total, 0.2) # 30.0
print(f"Сумма с налогом: {calc.add(total, tax)}") # 180.0
calc.history() # покажет все три операции
Задания для самостоятельной работы
Теория без практики бесполезна. Вот несколько заданий для закрепления материала — от простого к сложному
Задание 1. Калькулятор процентов
Напишите калькулятор, который умеет:
- Вычислять процент от числа (например, «15% от 200» = 30)
- Прибавлять процент к числу (например, «200 + 15%» = 230)
- Вычитать процент из числа (например, «200 — 15%» = 170)
Подсказка: создайте три отдельные функции:
percent_of(value, percent),add_percent(value, percent),subtract_percent(value, percent). Формула для процента от числа:value * percent / 100
Задание 2. Калькулятор с конвертацией
Добавьте к любому из примеров возможность конвертации:
- Километры в мили и обратно (1 км = 0.621371 миль)
- Цельсий в Фаренгейт и обратно (F = C * 9/5 + 32)
- Килограммы в фунты и обратно (1 кг = 2.20462 фунтов)
Подсказка: используйте словарь с коэффициентами конвертации. Для температуры понадобятся отдельные функции, так как перевод нелинейный
Задание 3. Калькулятор с сохранением истории в файл
Модифицируйте ООП-калькулятор так, чтобы история вычислений сохранялась в текстовый файл history.txt и загружалась при запуске.
Подсказка: используйте контекстный менеджер
with open("history.txt", "a") as f:для дозаписи в файл. При запуске программы прочитайте файл и выведите последние 5 записей
Задание 4. Калькулятор систем счисления
Создайте калькулятор, который работает с числами в разных системах счисления:
- Двоичная (ввод:
0b1010) - Восьмеричная (ввод:
0o12) - Шестнадцатеричная (ввод:
0xFF)
Подсказка: функция
int("0b1010", 2)преобразует двоичную строку в целое число. Встроенные функцииbin(),oct(),hex()выполняют обратное преобразование
Частые ошибки новичков и как их избежать
Разберём типичные ошибки, которые допускают начинающие программисты при создании калькулятора на Python
Ошибка 1: забыли преобразовать ввод в число
# НЕПРАВИЛЬНО — input() возвращает строку!
num1 = input("Число: ")
num2 = input("Число: ")
print(num1 + num2) # Выведет "105", а не 15 (конкатенация строк)
# ПРАВИЛЬНО
num1 = float(input("Число: "))
num2 = float(input("Число: "))
print(num1 + num2) # Выведет 15.0
ункция input() всегда возвращает строку. Оператор + для строк — это конкатенация (склеивание), а не сложение. Если ввести 10 и 5, получите "10" + "5" = "105". Всегда оборачивайте input() в int() или float()
Ошибка 2: деление на ноль без проверки
# НЕПРАВИЛЬНО — программа упадёт при вводе 0
result = num1 / num2 # ZeroDivisionError!
# ПРАВИЛЬНО — проверяем перед делением
if num2 != 0:
result = num1 / num2
else:
print("Ошибка: деление на ноль!")
Ошибка 3: использование = вместо ==
# НЕПРАВИЛЬНО — это присваивание, а не сравнение
if operation = "+": # SyntaxError!
...
# ПРАВИЛЬНО — двойной знак равенства для сравнения
if operation == "+":
...
Одиночный = — оператор присваивания, двойной == — оператор сравнения. В условиях if всегда используется ==
Ошибка 4: неправильные отступы
# НЕПРАВИЛЬНО — IndentationError
if operation == "+":
result = num1 + num2 # нет отступа!
# ПРАВИЛЬНО — 4 пробела отступа
if operation == "+":
result = num1 + num2
Python использует отступы для обозначения блоков кода. Стандарт — 4 пробела. Не смешивайте пробелы и табуляции — это приведёт к ошибке TabError
Ошибка 5: использование eval() для вычислений
# ОПАСНО — eval() может выполнить ЛЮБОЙ код!
result = eval(input("Выражение: "))
# Если пользователь введёт __import__('os').system('rm -rf /'),
# будут удалены файлы на диске!
# БЕЗОПАСНО — используйте явные операции
# или парсер выражений (как в примере 7 этой статьи)
eval()— одна из самых опасных функций в Python. Она выполняет произвольный код, переданный как строка. Никогда не используйтеeval()` с пользовательским вводом. Для учебных проектов, которые никогда не выйдут в продакшен, это допустимо, но лучше сразу привыкать к безопасным подходам
Ошибка 6: потеря точности при работе с float
# Неожиданный результат:
print(0.1 + 0.2) # 0.30000000000000004, а не 0.3!
# Решение для финансовых расчётов:
from decimal import Decimal
result = Decimal("0.1") + Decimal("0.2") # 0.3 (точно!)
# Решение для обычных случаев:
result = round(0.1 + 0.2, 10) # 0.3
Числа с плавающей точкой (float) хранятся в двоичной системе и не могут точно представить некоторые десятичные дроби. Это не ошибка Python — такое поведение одинаково во всех языках программирования. Для финансовых расчётов используйте модуль decimal
В этой статье мы создали калькулятор на Python девятью разными способами — от простейшего в 15 строк до полноценного GUI-приложения с безопасным парсером выражений. Вот что мы рассмотрели:
- Базовый калькулятор — переменные, ввод/вывод, условия
if/elif/else - Калькулятор с циклом —
while True,break,continue - Калькулятор с функциями — DRY-принцип, словарь функций,
if __name__ == "__main__" - Калькулятор с
match/case— современный синтаксис Python 3.10+ - Обработка ошибок —
try/except, пользовательские исключения, иерархия классов - Продвинутый калькулятор — модуль
math, тригонометрия, факториал, константы - GUI-калькулятор — Tkinter, безопасный парсер вместо
eval(), обработка событий - ООП-калькулятор — классы,
@dataclass,@property, история и память
Калькулятор — это проект, к которому стоит возвращаться по мере изучения Python. Каждая новая тема, которую вы осваиваете (работа с файлами, сети, базы данных, веб-фреймворки), может быть применена к калькулятору. Например, вы можете:
- Сделать веб-калькулятор на Flask или Django
- Добавить сохранение истории в SQLite
- Создать Telegram-бота с функциями калькулятора
- Написать модульные тесты с помощью
pytest
Все примеры из этой статьи можно скачать и запустить — они работают без дополнительных зависимостей (кроме стандартной библиотеки Python). Если у вас возникли вопросы, пишите в комментариях.
Удачи в изучении Python!