В этом проекте мы создадим полнофункциональный калькулятор с графическим интерфейсом пользователя (GUI). Этот проект объединит знания по работе с функциями, обработке исключений, объектно-ориентированным программированием и использованию внешних библиотек. Мы будем использовать библиотеку tkinter для создания интерфейса.
Перед началом реализации определим основные компоненты калькулятора:
Создадим класс Calculator, который будет содержать всю логику работы калькулятора:
import tkinter as tk
from tkinter import messagebox
import math
class Calculator:
def __init__(self):
self.окно = tk.Tk()
self.окно.title("Калькулятор")
self.окно.geometry("300x400")
self.окно.resizable(False, False)
# Переменные для хранения состояния
self.текущее_значение = "0"
self.предыдущее_значение = ""
self.операция = ""
self.новый_ввод = True
self.создать_интерфейс()
def создать_интерфейс(self):
# Дисплей
self.дисплей = tk.Entry(
self.окно,
font=("Arial", 20),
justify="right",
state="readonly",
bg="white"
)
self.дисплей.grid(row=0, column=0, columnspan=4, padx=10, pady=10, sticky="ew")
self.обновить_дисплей()
# Кнопки
кнопки = [
('C', 1, 0, 'clear'),
('←', 1, 1, 'backspace'),
('/', 1, 2, 'операция'),
('*', 1, 3, 'операция'),
('7', 2, 0, 'цифра'),
('8', 2, 1, 'цифра'),
('9', 2, 2, 'цифра'),
('-', 2, 3, 'операция'),
('4', 3, 0, 'цифра'),
('5', 3, 1, 'цифра'),
('6', 3, 2, 'цифра'),
('+', 3, 3, 'операция'),
('1', 4, 0, 'цифра'),
('2', 4, 1, 'цифра'),
('3', 4, 2, 'цифра'),
('=', 4, 3, 'равно', 2),
('0', 5, 0, 'цифра', 2),
('.', 5, 2, 'точка'),
]
for (текст, строка, столбец, тип_кнопки, *args) in кнопки:
if тип_кнопки == 'цифра':
команда = lambda x=текст: self.ввод_цифры(x)
elif тип_кнопки == 'операция':
команда = lambda x=текст: self.ввод_операции(x)
elif тип_кнопки == 'равно':
команда = self.вычислить
elif тип_кнопки == 'clear':
команда = self.очистить
elif тип_кнопки == 'backspace':
команда = self.удалить_символ
elif тип_кнопки == 'точка':
команда = self.ввод_точки
кнопка = tk.Button(
self.окно,
text=текст,
font=("Arial", 14),
command=команда
)
# Определяем размер кнопки
columnspan = args[0] if args else 1
if текст == '=':
кнопка.grid(row=строка, column=столбец, columnspan=columnspan,
padx=2, pady=2, sticky="nsew")
elif текст == '0':
кнопка.grid(row=строка, column=столбец, columnspan=columnspan,
padx=2, pady=2, sticky="nsew")
else:
кнопка.grid(row=строка, column=столбец, columnspan=columnspan,
padx=2, pady=2, sticky="nsew")
# Настройка сетки
for i in range(6):
self.окно.grid_rowconfigure(i, weight=1)
for i in range(4):
self.окно.grid_columnconfigure(i, weight=1)
def обновить_дисплей(self):
self.дисплей.config(state="normal")
self.дисплей.delete(0, tk.END)
self.дисплей.insert(0, self.текущее_значение)
self.дисплей.config(state="readonly")
def ввод_цифры(self, цифра):
if self.новый_ввод:
self.текущее_значение = цифра
self.новый_ввод = False
else:
if self.текущее_значение == "0":
self.текущее_значение = цифра
else:
self.текущее_значение += цифра
self.обновить_дисплей()
def ввод_точки(self):
if self.новый_ввод:
self.текущее_значение = "0."
self.новый_ввод = False
elif "." not in self.текущее_значение:
self.текущее_значение += "."
self.обновить_дисплей()
def ввод_операции(self, операция):
if self.операция and not self.новый_ввод:
self.вычислить()
self.предыдущее_значение = self.текущее_значение
self.операция = операция
self.новый_ввод = True
def вычислить(self):
try:
if self.операция and self.предыдущее_значение:
выражение = f"{self.предыдущее_значение} {self.операция} {self.текущее_значение}"
# Заменяем символы для eval
выражение = выражение.replace("×", "*").replace("÷", "/")
результат = eval(выражение)
# Форматируем результат
if результат == int(результат):
self.текущее_значение = str(int(результат))
else:
self.текущее_значение = str(round(результат, 10))
self.операция = ""
self.предыдущее_значение = ""
self.новый_ввод = True
self.обновить_дисплей()
except Exception as e:
messagebox.showerror("Ошибка", "Неверное выражение")
self.очистить()
def очистить(self):
self.текущее_значение = "0"
self.предыдущее_значение = ""
self.операция = ""
self.новый_ввод = True
self.обновить_дисплей()
def удалить_символ(self):
if not self.новый_ввод:
if len(self.текущее_значение) > 1:
self.текущее_значение = self.текущее_значение[:-1]
else:
self.текущее_значение = "0"
self.новый_ввод = True
self.обновить_дисплей()
def запустить(self):
self.окно.mainloop()
# Запуск калькулятора
if __name__ == "__main__":
калькулятор = Calculator()
калькулятор.запустить()
Добавим дополнительные функции и улучшим интерфейс:
import tkinter as tk
from tkinter import messagebox
import math
class AdvancedCalculator:
def __init__(self):
self.окно = tk.Tk()
self.окно.title("Продвинутый Калькулятор")
self.окно.geometry("400x500")
self.окно.resizable(False, False)
self.окно.configure(bg="#f0f0f0")
# Переменные для хранения состояния
self.текущее_значение = "0"
self.предыдущее_значение = ""
self.операция = ""
self.новый_ввод = True
self.выражение = ""
self.создать_интерфейс()
def создать_интерфейс(self):
# Дисплей для выражения
self.дисплей_выражения = tk.Entry(
self.окно,
font=("Arial", 12),
justify="right",
state="readonly",
bg="#e0e0e0",
relief="flat"
)
self.дисплей_выражения.grid(row=0, column=0, columnspan=5,
padx=10, pady=(10, 0), sticky="ew")
# Основной дисплей
self.дисплей = tk.Entry(
self.окно,
font=("Arial", 20, "bold"),
justify="right",
state="readonly",
bg="white",
relief="solid",
bd=2
)
self.дисплей.grid(row=1, column=0, columnspan=5,
padx=10, pady=(0, 10), sticky="ew")
self.обновить_дисплей()
# Кнопки
кнопки = [
# Первая строка - дополнительные функции
('C', 2, 0, 'clear', '#ff6666'),
('CE', 2, 1, 'очистить_ввод', '#ff9999'),
('⌫', 2, 2, 'backspace', '#ffcc99'),
('÷', 2, 3, 'операция', '#ffcc99'),
('√', 2, 4, 'функция', '#99ccff'),
# Вторая строка
('7', 3, 0, 'цифра', '#ffffff'),
('8', 3, 1, 'цифра', '#ffffff'),
('9', 3, 2, 'цифра', '#ffffff'),
('×', 3, 3, 'операция', '#ffcc99'),
('x²', 3, 4, 'функция', '#99ccff'),
# Третья строка
('4', 4, 0, 'цифра', '#ffffff'),
('5', 4, 1, 'цифра', '#ffffff'),
('6', 4, 2, 'цифра', '#ffffff'),
('-', 4, 3, 'операция', '#ffcc99'),
('1/x', 4, 4, 'функция', '#99ccff'),
# Четвертая строка
('1', 5, 0, 'цифра', '#ffffff'),
('2', 5, 1, 'цифра', '#ffffff'),
('3', 5, 2, 'цифра', '#ffffff'),
('+', 5, 3, 'операция', '#ffcc99'),
('=', 5, 4, 'равно', '#66cc99', 2),
# Пятая строка
('±', 6, 0, 'функция', '#ffffff'),
('0', 6, 1, 'цифра', '#ffffff', 2),
('.', 6, 3, 'точка', '#ffffff'),
]
for элемент in кнопки:
if len(элемент) == 5:
текст, строка, столбец, тип_кнопки, цвет = элемент
rowspan = 1
columnspan = 1
elif len(элемент) == 6:
текст, строка, столбец, тип_кнопки, цвет, размер = элемент
if размер == 2:
rowspan = 1
columnspan = 2
else:
rowspan = размер
columnspan = 1
else:
continue
# Определяем команду для кнопки
if тип_кнопки == 'цифра':
команда = lambda x=текст: self.ввод_цифры(x)
elif тип_кнопки == 'операция':
команда = lambda x=текст: self.ввод_операции(x)
elif тип_кнопки == 'равно':
команда = self.вычислить
elif тип_кнопки == 'clear':
команда = self.очистить
elif тип_кнопки == 'очистить_ввод':
команда = self.очистить_ввод
elif тип_кнопки == 'backspace':
команда = self.удалить_символ
elif тип_кнопки == 'точка':
команда = self.ввод_точки
elif тип_кнопки == 'функция':
if текст == '√':
команда = lambda: self.применить_функцию('sqrt')
elif текст == 'x²':
команда = lambda: self.применить_функцию('square')
elif текст == '1/x':
команда = lambda: self.применить_функцию('reciprocal')
elif текст == '±':
команда = self.сменить_знак
else:
команда = lambda: None
кнопка = tk.Button(
self.окно,
text=текст,
font=("Arial", 12, "bold"),
command=команда,
bg=цвет,
relief="raised",
bd=1
)
# Размещаем кнопку
if текст == '=':
кнопка.grid(row=строка, column=столбец, rowspan=rowspan, columnspan=columnspan,
padx=2, pady=2, sticky="nsew")
elif текст == '0':
кнопка.grid(row=строка, column=столбец, columnspan=columnspan,
padx=2, pady=2, sticky="nsew")
else:
кнопка.grid(row=строка, column=столбец, columnspan=columnspan,
padx=2, pady=2, sticky="nsew")
# Настройка сетки
for i in range(7):
self.окно.grid_rowconfigure(i, weight=1)
for i in range(5):
self.окно.grid_columnconfigure(i, weight=1)
def обновить_дисплей(self):
self.дисплей.config(state="normal")
self.дисплей.delete(0, tk.END)
self.дисплей.insert(0, self.текущее_значение)
self.дисплей.config(state="readonly")
self.дисплей_выражения.config(state="normal")
self.дисплей_выражения.delete(0, tk.END)
self.дисплей_выражения.insert(0, self.выражение)
self.дисплей_выражения.config(state="readonly")
def ввод_цифры(self, цифра):
if self.новый_ввод:
self.текущее_значение = цифра
self.новый_ввод = False
else:
if self.текущее_значение == "0":
self.текущее_значение = цифра
else:
self.текущее_значение += цифра
self.обновить_дисплей()
def ввод_точки(self):
if self.новый_ввод:
self.текущее_значение = "0."
self.новый_ввод = False
elif "." not in self.текущее_значение:
self.текущее_значение += "."
self.обновить_дисплей()
def ввод_операции(self, операция):
if self.операция and not self.новый_ввод:
self.вычислить()
self.предыдущее_значение = self.текущее_значение
self.операция = операция
self.выражение = f"{self.предыдущее_значение} {операция}"
self.новый_ввод = True
self.обновить_дисплей()
def вычислить(self):
try:
if self.операция and self.предыдущее_значение:
выражение = f"{self.предыдущее_значение} {self.операция} {self.текущее_значение}"
# Заменяем символы для eval
выражение = выражение.replace("×", "*").replace("÷", "/")
результат = eval(выражение)
# Форматируем результат
if результат == int(результат):
self.текущее_значение = str(int(результат))
else:
self.текущее_значение = str(round(результат, 10))
self.выражение = f"{self.предыдущее_значение} {self.операция} {self.текущее_значение} ="
self.операция = ""
self.предыдущее_значение = ""
self.новый_ввод = True
self.обновить_дисплей()
except Exception as e:
messagebox.showerror("Ошибка", "Неверное выражение")
self.очистить()
def применить_функцию(self, функция):
try:
значение = float(self.текущее_значение)
if функция == 'sqrt':
if значение < 0:
raise ValueError("Невозможно извлечь корень из отрицательного числа")
результат = math.sqrt(значение)
elif функция == 'square':
результат = значение ** 2
elif функция == 'reciprocal':
if значение == 0:
raise ZeroDivisionError("Деление на ноль")
результат = 1 / значение
# Форматируем результат
if результат == int(результат):
self.текущее_значение = str(int(результат))
else:
self.текущее_значение = str(round(результат, 10))
self.новый_ввод = True
self.обновить_дисплей()
except ValueError as e:
messagebox.showerror("Ошибка", str(e))
self.очистить()
except ZeroDivisionError as e:
messagebox.showerror("Ошибка", str(e))
self.очистить()
def сменить_знак(self):
if self.текущее_значение != "0":
if self.текущее_значение.startswith("-"):
self.текущее_значение = self.текущее_значение[1:]
else:
self.текущее_значение = "-" + self.текущее_значение
self.обновить_дисплей()
def очистить(self):
self.текущее_значение = "0"
self.предыдущее_значение = ""
self.операция = ""
self.выражение = ""
self.новый_ввод = True
self.обновить_дисплей()
def очистить_ввод(self):
self.текущее_значение = "0"
self.новый_ввод = True
self.обновить_дисплей()
def удалить_символ(self):
if not self.новый_ввод:
if len(self.текущее_значение) > 1:
self.текущее_значение = self.текущее_значение[:-1]
else:
self.текущее_значение = "0"
self.новый_ввод = True
self.обновить_дисплей()
def запустить(self):
self.окно.mainloop()
# Запуск калькулятора
if __name__ == "__main__":
калькулятор = AdvancedCalculator()
калькулятор.запустить()
Добавьте в калькулятор функцию процента (%), которая вычисляет процент от текущего значения.
# Добавьте этот метод в класс AdvancedCalculator:
def процент(self):
try:
значение = float(self.текущее_значение)
результат = значение / 100
if результат == int(результат):
self.текущее_значение = str(int(результат))
else:
self.текущее_значение = str(round(результат, 10))
self.новый_ввод = True
self.обновить_дисплей()
except ValueError:
messagebox.showerror("Ошибка", "Неверное значение")
self.очистить()
# Добавьте кнопку в интерфейс:
# В список кнопок добавьте:
('%', 2, 4, 'функция', '#99ccff'),
# В обработчике функций добавьте:
elif текст == '%':
команда = self.процент
Добавьте возможность просмотра истории вычислений в калькуляторе.
# Добавьте в __init__:
self.история = []
# Добавьте метод для сохранения в историю:
def сохранить_в_историю(self, выражение, результат):
self.история.append(f"{выражение} = {результат}")
if len(self.история) > 10: # Ограничиваем историю 10 записями
self.история.pop(0)
# В методе вычислить() после вычисления результата добавьте:
self.сохранить_в_историю(выражение, self.текущее_значение)
# Добавьте метод для показа истории:
def показать_историю(self):
if not self.история:
messagebox.showinfo("История", "История пуста")
return
история_текст = "\n".join(self.история)
messagebox.showinfo("История вычислений", история_текст)
# Добавьте кнопку для истории в интерфейс:
('HIST', 1, 4, 'история', '#cccccc'),
# В обработчике кнопок добавьте:
elif тип_кнопки == 'история':
команда = self.показать_историю
Добавьте поддержку ввода с клавиатуры в калькулятор.
# Добавьте в метод создания интерфейса:
self.окно.bind("<Key>", self.обработка_клавиши)
# Добавьте метод обработки клавиш:
def обработка_клавиши(self, event):
клавиша = event.char
if клавиша.isdigit():
self.ввод_цифры(клавиша)
elif клавиша == ".":
self.ввод_точки()
elif клавиша in ["+", "-", "*", "/"]:
операция_отображение = {"*": "×", "/": "÷"}.get(клавиша, клавиша)
self.ввод_операции(операция_отображение)
elif клавиша == "\r" or клавиша == "=": # Enter или =
self.вычислить()
elif клавиша == "\x08": # Backspace
self.удалить_символ()
elif клавиша == "\x1b": # Escape
self.очистить()