Калькулятор на Python пишется в три шага: прочитать два числа через input(), выбрать операцию условием if/elif и вывести результат через print(). Работающий набросок занимает меньше двадцати строк, но падает от первой опечатки.
Все примеры запускались на Python 3.14.3 в Windows 10, вывод скопирован из консоли. Если нужен совсем короткий калькулятор с вводом примера одной строкой, он есть в подборке простых программ на Python.
- Подготовка: версия Python и среда разработки
- Почему input возвращает текст
- Простой калькулятор на Python: условные операторы if и elif
- Калькулятор на функциях
- Цикл и обработка ошибок: полный код программы
- Строковый калькулятор: почему не eval
- Тестирование калькулятора
- Калькулятор с графическим интерфейсом на tkinter
- Частые вопросы
- Как написать калькулятор на Python в одну строку?
- Как добавить квадратный корень?
- Почему -2 ** 2 даёт -4?
- int или float: что взять для калькулятора?
Подготовка: версия Python и среда разработки
Понадобится Python 3.8 или новее и любой редактор. Нижняя граница из-за строкового калькулятора: он опирается на узел ast.Constant, которым числа в дереве разбора представлены начиная с версии 3.8. Проверяли мы код только на 3.14.3. Старые примеры с ast.Num в Python 3.14 не запустятся: этот класс удалён. Установить интерпретатор можно с сайта python.org или из пакетов системы. Код сохраняйте в файл calc.py и запускайте командой python calc.py из папки с файлом. Если хотите писать код с подсветкой и запускать его одной кнопкой, настройка среды разработки разобрана в статье про VS Code для Python.
Почему input возвращает текст
Функция input() всегда возвращает строку, даже если пользователь ввёл цифры:
a = input("Введите первое число: ")
b = input("Введите второе число: ")
print(a + b)
Введите первое число: 2
Введите второе число: 3
23
Числа не сложились, а склеились как текст: для строк + означает объединение. Перед вычислениями строку нужно привести к числу: для этого используют float() или int(). Калькулятору лучше подходит float(), потому что int() дробь не читает.
Простой калькулятор на Python: условные операторы if и elif
Программа через input() просит первое число, второе число и знак операции, а дальше условие выбирает нужную операцию. Каждая ветка elif делает одно вычисление:
a = float(input("Введите первое число: "))
b = float(input("Введите второе число: "))
operation = input("Выберите операцию (+ - * /): ")
if operation == "+":
result = a + b
elif operation == "-":
result = a - b
elif operation == "*":
result = a * b
elif operation == "/":
result = a / b
else:
result = None
print("Неизвестная операция:", operation)
if result is not None:
print(f"Результат: {result}")
Так выглядит работа программы:
Введите первое число: 7
Введите второе число: 2
Выберите операцию (+ - * /): /
Результат: 3.5
Ветка else ловит любой знак, которого нет в списке, а result = None нужен, чтобы последний print не выводил несуществующий результат. Пока пользователь вводит то, что от него ждут, калькулятор работает. Теперь попробуйте ввести слово вместо числа или поделить на ноль. Ниже последние строки двух запусков, трассировку выше мы сократили:
Введите первое число: семь
ValueError: could not convert string to float: 'семь'
Введите первое число: 5
Введите второе число: 0
Выберите операцию (+ - * /): /
ZeroDivisionError: division by zero
В обоих случаях программа завершается с кодом 1, и пользователь не получает ни результата, ни понятного сообщения. Чинить это проверками if b == 0 перед каждой операцией неудобно, проще поймать исключение там, где оно возникает.

Калькулятор на функциях
Вычисление и ввод полезно разнести по отдельным функциям. calculate() принимает два числа и знак операции и возвращает результат. Такое разделение позволяет вызвать её из консоли, из теста или из окна с кнопками, а чтобы изменить расчёт, править придётся одно место. Заодно добавим остаток от деления % и возведение в степень **:
import math
def calculate(a, b, operation):
if operation == "+":
result = a + b
elif operation == "-":
result = a - b
elif operation == "*":
result = a * b
elif operation == "/":
result = a / b
elif operation == "%":
result = a % b
elif operation == "**":
result = a ** b
else:
raise ValueError(f"нет такой операции: {operation}")
if isinstance(result, complex):
raise ValueError("дробная степень отрицательного числа")
if not math.isfinite(result): # 1e308 * 10 даёт inf без исключения
raise OverflowError("результат слишком большой")
return result
def read_number(prompt):
while True:
text = input(prompt).strip().replace(",", ".")
try:
number = float(text)
except ValueError:
print("Это не число, попробуйте ещё раз")
continue
if not math.isfinite(number):
print("nan и inf не подходят, нужно обычное число")
continue
return number
Деление на ноль calculate() не проверяет сама: интерпретатор поднимает ZeroDivisionError и для /, и для %, и для 0 ** -1, а ловить это исключение будет вызывающий код.
(-8) ** 0.5 возвращает комплексное число без всякой ошибки, и дальше калькулятор вывел бы (1.7319121124709868e-16+2.8284271247461903j). Поэтому функция проверяет тип результата и поднимает понятную ошибку. Каждая ветка теперь только считает result, а проверки стоят один раз в конце.
В read_number() три защиты. replace(",", ".") разрешает вводить 0,5: дроби у нас привыкли писать через запятую, а float("0,5") падает. Цикл while True вызывает input() снова, пока ввод не прочитается. Проверка math.isfinite() закрывает менее очевидную дыру: float() без ошибок принимает строки nan, inf и Infinity в любом регистре, и без проверки они попали бы в расчёт.

Цикл и обработка ошибок: полный код программы
Соберём функции в одну программу. Она предлагает выбор операции, затем спрашивает два числа и считает несколько примеров подряд, пока пользователь не введёт q. Выход из цикла здесь двойной: по команде и по концу ввода. Второй нужен, если программе подали ввод из файла без q в конце: тогда input() поднимает EOFError.
OPERATIONS = ("+", "-", "*", "/", "%", "**")
def main():
print("Калькулятор. Для выхода введите q")
while True:
try:
operation = input("Выберите операцию (+ - * / % **): ").strip()
if operation == "q":
break
if operation not in OPERATIONS:
print("Такой операции нет")
continue
a = read_number("Введите первое число: ")
b = read_number("Введите второе число: ")
except EOFError: # ввод кончился: файл или поток без q в конце
print()
break
try:
result = calculate(a, b, operation)
except ZeroDivisionError:
print("Ошибка: деление на ноль")
except OverflowError:
print("Ошибка: результат слишком большой")
except ValueError as error:
print("Ошибка:", error)
else:
print(f"Результат: {a:.12g} {operation} {b:.12g} = {result:.12g}")
print("Калькулятор закрыт")
main()
Сеанс, в котором проверены все ветки:
Калькулятор. Для выхода введите q
Выберите операцию (+ - * / % **): /
Введите первое число: 10
Введите второе число: 4
Результат: 10 / 4 = 2.5
Выберите операцию (+ - * / % **): /
Введите первое число: 5
Введите второе число: 0
Ошибка: деление на ноль
Выберите операцию (+ - * / % **): %
Введите первое число: 7
Введите второе число: 3
Результат: 7 % 3 = 1
Выберите операцию (+ - * / % **): **
Введите первое число: 2
Введите второе число: 10
Результат: 2 ** 10 = 1024
Выберите операцию (+ - * / % **): **
Введите первое число: 10
Введите второе число: 400
Ошибка: результат слишком большой
Выберите операцию (+ - * / % **): *
Введите первое число: 1e308
Введите второе число: 10
Ошибка: результат слишком большой
Выберите операцию (+ - * / % **): **
Введите первое число: -8
Введите второе число: 0.5
Ошибка: дробная степень отрицательного числа
Выберите операцию (+ - * / % **): +
Введите первое число: семь
Это не число, попробуйте ещё раз
Введите первое число: 0,1
Введите второе число: 0.2
Результат: 0.1 + 0.2 = 0.3
Выберите операцию (+ - * / % **): +
Введите первое число: nan
nan и inf не подходят, нужно обычное число
Введите первое число: 1
Введите второе число: 2
Результат: 1 + 2 = 3
Выберите операцию (+ - * / % **): ^
Такой операции нет
Выберите операцию (+ - * / % **): q
Калькулятор закрыт
Переполнение float проявляется двумя способами. 10.0 ** 400 поднимает OverflowError, а умножение 1e308 * 10 молча даёт inf. Второй случай ловит проверка math.isfinite() в конце calculate(): она превращает inf и nan в тот же OverflowError, и обе ошибки обрабатывает одна ветка except. Формат :.12g в f-строке выводит до 12 значащих цифр без хвостовых нулей, поэтому 10.0 печатается как 10, а сумма 0.1 + 0.2 как 0.3 вместо 0.30000000000000004.
Блок else после try выполняется, только если исключения не было. Каждое исключение поймано своим классом. Голый except: поймал бы и NameError от опечатки в имени переменной, и вместо трассировки с номером строки вы видели бы только своё сообщение об ошибке.
Если цикл while True с break и continue пока читается тяжело, посмотрите разбор циклов с условием.

Строковый калькулятор: почему не eval
Многие примеры в сети считают выражение целиком через eval(input()). Работает это так:
print(eval("2 + 3 * 4"))
print(eval("__import__('platform').python_version()"))
14
3.14.3
Во второй строке уже код: eval импортировал модуль и вызвал функцию. Тем же способом пользователь выполнит любой код, в том числе удаление файлов или запуск программ. Часто советуют «обезвредить» его, передав пустой словарь __builtins__. Это не защита:
trick = "[c.__name__ for c in ().__class__.__base__.__subclasses__()][:4]"
print(eval(trick, {"__builtins__": {}}))
['type', 'async_generator', 'bytearray_iterator', 'bytearray']
Без единого встроенного имени выражение добралось до классов интерпретатора: подмена __builtins__ ограничивает только доступные имена, объекты интерпретатора остаются в досягаемости. Мы не советуем eval для калькулятора ни в каком виде. ast.literal_eval тоже не выход: на строке 1 + 2 он падает с ValueError: malformed node or string, потому что операторы не вычисляет.

ast разбирает строку в дерево, а eval_node считает только разрешённое: числа, скобки, шесть операций и унарные плюс и минус. Всё остальное (имена, вызовы функций, атрибуты) отклоняется:
import ast
import math
import operator
BIN_OPS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Mod: operator.mod,
ast.Pow: operator.pow,
}
UNARY_OPS = {ast.UAdd: operator.pos, ast.USub: operator.neg}
def evaluate(text):
if len(text) > 100:
raise ValueError("слишком длинное выражение")
tree = ast.parse(text, mode="eval")
result = eval_node(tree.body)
if not math.isfinite(result):
raise OverflowError("слишком большое число")
return result
def eval_node(node):
if isinstance(node, ast.Constant) and type(node.value) in (int, float):
return float(node.value)
if isinstance(node, ast.UnaryOp) and type(node.op) in UNARY_OPS:
return UNARY_OPS[type(node.op)](eval_node(node.operand))
if isinstance(node, ast.BinOp) and type(node.op) in BIN_OPS:
left = eval_node(node.left)
right = eval_node(node.right)
result = BIN_OPS[type(node.op)](left, right)
if isinstance(result, complex):
raise ValueError("дробная степень отрицательного числа")
return result
raise ValueError("можно только числа, скобки и + - * / % **")
def calc_line(text):
try:
return f"{evaluate(text):.12g}"
except ZeroDivisionError:
return "Ошибка: деление на ноль"
except OverflowError:
return "Ошибка: слишком большое число"
except SyntaxError:
return "Ошибка: выражение записано неверно"
except ValueError as error:
return f"Ошибка: {error}"
ast.parse(..., mode="eval") превращает строку в дерево: 2 + 3 * 4 становится узлом BinOp со сложением, правая часть которого сама BinOp с умножением. Функция eval_node обходит дерево рекурсивно. Числа переводятся во float намеренно: выражение 9 ** 9 ** 9 с целыми числами не досчиталось и за 15 секунд, мы прервали процесс, а с float сразу поднимает OverflowError. Переполнение без исключения, как 1e200 * 1e200, ловит та же проверка math.isfinite(), что и в консольной версии. Без ограничения в 100 символов строка из тысячи минусов перед единицей проходит ast.parse, но роняет eval_node с RecursionError: maximum recursion depth exceeded, а из ста тысяч уже ast.parse падает с MemoryError.
Проверка на хороших и плохих выражениях:
examples = ["2 + 3 * 4", "(1 + 2) / 3", "0.1 + 0.2", "-2 ** 2", "7 % 3",
"1 / 0", "9 ** 9 ** 9", "1e200 * 1e200", "(-8) ** 0.5", "2 +",
"__import__('os')"]
for text in examples:
print(f"{text:20} -> {calc_line(text)}")
2 + 3 * 4 -> 14
(1 + 2) / 3 -> 1
0.1 + 0.2 -> 0.3
-2 ** 2 -> -4
7 % 3 -> 1
1 / 0 -> Ошибка: деление на ноль
9 ** 9 ** 9 -> Ошибка: слишком большое число
1e200 * 1e200 -> Ошибка: слишком большое число
(-8) ** 0.5 -> Ошибка: дробная степень отрицательного числа
2 + -> Ошибка: выражение записано неверно
__import__('os') -> Ошибка: можно только числа, скобки и + - * / % **
Результат -2 ** 2 = -4 не ошибка калькулятора. Степень связывается сильнее унарного минуса слева, поэтому выражение читается как -(2 ** 2). Нужен квадрат минус двух, пишите (-2) ** 2. Подробнее о том, как разбирают выражения из строки, есть в статье про парсер математических выражений.
Тестирование калькулятора
Раз вычисление живёт в функциях, код можно проверить без ручного ввода. Инструкция assert молчит, если условие верно, и останавливает программу с AssertionError, если нет:
assert calculate(7, 2, "/") == 3.5
assert calculate(2, 10, "**") == 1024
assert calc_line("2 + 2 * 2") == "6"
assert calc_line("5 / 0") == "Ошибка: деление на ноль"
assert calc_line("open('file.txt')").startswith("Ошибка")
print("Все проверки прошли")
Все проверки прошли
Когда добавите кнопку % в окно из следующего раздела или новую операцию в calculate(), допишите для неё строку assert и прогоните проверки заново.
Калькулятор с графическим интерфейсом на tkinter
Модуль tkinter есть в Windows и в большинстве Unix-систем, официальные установщики Python идут вместе с Tcl/Tk 8.6. При этом модуль необязательный, и в некоторых сборках его нет. Проверить просто: команда python -m tkinter должна открыть маленькое тестовое окно. Если модуля нет, в Linux поставьте пакет tkinter из репозитория дистрибутива.
Логику кнопок выносим в отдельную функцию press(): она получает текст на экране и нажатую кнопку и возвращает новый текст. Для вычисления код окна берёт calc_line() из предыдущего раздела, поэтому eval здесь тоже нет.
import tkinter as tk
def press(display, button):
if display.startswith("Ошибка"):
display = ""
if button == "C":
return ""
if button == "←":
return display[:-1]
if button == "=":
return calc_line(display) if display else ""
return display + button
KEYS = [
["7", "8", "9", "/"],
["4", "5", "6", "*"],
["1", "2", "3", "-"],
["0", ".", "=", "+"],
["C", "←", "(", ")"],
]
def build_window():
root = tk.Tk()
root.title("Калькулятор")
screen = tk.StringVar()
entry = tk.Entry(root, textvariable=screen, font=("Arial", 20), justify="right")
entry.grid(row=0, column=0, columnspan=4, sticky="we", padx=5, pady=5)
for row_number, row in enumerate(KEYS, start=1):
for column, label in enumerate(row):
button = tk.Button(
root, text=label, width=5, height=2, font=("Arial", 14),
command=lambda b=label: screen.set(press(screen.get(), b)),
)
button.grid(row=row_number, column=column, padx=2, pady=2)
root.bind("<Return>", lambda event: screen.set(press(screen.get(), "=")))
return root
tk.Tk() создаёт главное окно, Entry служит экраном калькулятора, а метод grid раскладывает 20 кнопок по сетке 4 x 5 под ним. В lambda b=label подпись кнопки запоминается в момент создания. Без b=label все лямбды взяли бы последнее значение переменной цикла, и каждая кнопка вставляла бы ). Привязка <Return> добавляет поддержку клавиши Enter: она запускает расчёт, так что пользователь сможет печатать выражение и с клавиатуры, в том числе **, для которого нет кнопки.
Функцию press() проверяем без окна, как обычную функцию:
display = ""
for button in ["1", "2", "/", "0", "="]:
display = press(display, button)
print(display)
for button in ["7", "*", "(", "2", "+", "1", ")", "="]:
display = press(display, button)
print(display)
Ошибка: деление на ноль
21
После ошибки первое нажатие стирает сообщение, поэтому второй пример считается с чистого экрана. Окно запускают последние строки кода:
if __name__ == "__main__":
build_window().mainloop()
mainloop() показывает окно и обрабатывает нажатия, пока его не закроют. Мы открывали это окно в Windows 10 и нажимали кнопки программно: 12/0= дал «Ошибка: деление на ноль», C 2+3*4= дал 14, C 99 ← +1= дал 10, а набранное с клавиатуры 2**10 после Enter превратилось в 1024.

Кнопку % можно добавить, дописав её в список KEYS: остаток от деления calc_line() уже считает. Для практики на условиях и циклах подойдут задачи по Python для начинающих.

Частые вопросы
Как написать калькулятор на Python в одну строку?
Однострочник с eval из раздела про строковый калькулятор выполняет любой код из ввода, поэтому его мы не даём. Если функции evaluate() и calc_line() уже есть в файле, цикл ввода укладывается в одну строку:
while (line := input("Пример: ")) != "q": print(calc_line(line))
Пример: 2 ** 0.5
1.41421356237
Пример: q
Оператор := записывает введённую строку в line и тут же отдаёт её для сравнения с "q"; оператор есть с Python 3.8.
Как добавить квадратный корень?
Корень уже есть: возведите число в степень 0.5. В строковом калькуляторе 2 ** 0.5 даёт 1.41421356237. (-4) ** 0.5 выведет сообщение «дробная степень отрицательного числа», а без скобок -4 ** 0.5 даст -2.
Почему -2 ** 2 даёт -4?
Оператор ** связывается сильнее унарного минуса, стоящего слева, поэтому выражение считается как -(2 ** 2). Скобки (-2) ** 2 дают 4.
int или float: что взять для калькулятора?
Для целых чисел int считает точно и без ограничения размера, но дробь не читает: int("2.5") падает с ValueError: invalid literal for int() with base 10: '2.5'. Калькулятору с делением нужен float.








