Загрузка данных
import tkinter as tk
from tkinter import ttk, messagebox
import math
class CalculatorApp(tk.Tk):
"""Основной класс приложения Калькулятор (ООП)"""
def __init__(self):
super().__init__()
self.title("Калькулятор (Лабораторная №13)")
self.geometry("450x560")
self.resizable(False, False)
# --- Состояние приложения ---
self.expression = ""
self.precision = 4
self.angle_mode = tk.StringVar(value="rad") # rad или deg
self.mode = "standard" # standard или engineering
self.memory = [] # Память для хранения до 5 чисел
self.max_memory = 5
self._setup_ui()
self._create_main_menu()
self._create_context_menu()
# ==================== ИНТЕРФЕЙС ====================
def _setup_ui(self):
# Поле вывода
self.display_var = tk.StringVar()
self.display = ttk.Entry(self, textvariable=self.display_var, font=("Consolas", 18),
justify="right", state="readonly")
self.display.grid(row=0, column=0, columnspan=5, padx=10, pady=10, sticky="ew")
# Панель настроек (Точность, Углы)
ctrl_frame = ttk.LabelFrame(self, text="Настройки")
ctrl_frame.grid(row=1, column=0, columnspan=5, padx=10, pady=5, sticky="ew")
ttk.Label(ctrl_frame, text="Точность (знаков):").pack(side="left", padx=(5, 0))
self.prec_var = tk.IntVar(value=self.precision)
ttk.Spinbox(ctrl_frame, from_=0, to=10, textvariable=self.prec_var, width=3,
command=self._update_precision).pack(side="left", padx=5)
ttk.Radiobutton(ctrl_frame, text="RAD", variable=self.angle_mode, value="rad").pack(side="left", padx=5)
ttk.Radiobutton(ctrl_frame, text="DEG", variable=self.angle_mode, value="deg").pack(side="left", padx=5)
# Панель памяти
mem_frame = ttk.LabelFrame(self, text="Память (до 5 значений)")
mem_frame.grid(row=2, column=0, columnspan=5, padx=10, pady=5, sticky="ew")
ttk.Button(mem_frame, text="MC", command=self.mem_clear).pack(side="left", fill="x", expand=True)
ttk.Button(mem_frame, text="M+", command=lambda: self._mem_op("add")).pack(side="left", fill="x", expand=True) ttk.Button(mem_frame, text="M-", command=lambda: self._mem_op("sub")).pack(side="left", fill="x", expand=True)
ttk.Button(mem_frame, text="MR", command=self.mem_recall).pack(side="left", fill="x", expand=True)
ttk.Button(mem_frame, text="MS", command=self.mem_store).pack(side="left", fill="x", expand=True)
# Индикатор памяти
self.mem_label = ttk.Label(mem_frame, text="[Пусто]", foreground="gray")
self.mem_label.pack(side="right", padx=5)
# Контейнер кнопок
self.btn_frame = ttk.Frame(self)
self.btn_frame.grid(row=3, column=0, columnspan=5, padx=10, pady=10, sticky="nsew")
self.columnconfigure(0, weight=1)
self.rowconfigure(3, weight=1)
self._build_buttons()
def _build_buttons(self):
# Очистка старых кнопок
for w in self.btn_frame.winfo_children():
w.destroy()
# Стандартные кнопки
std_buttons = [
("C", 0, 0), ("⌫", 0, 1), ("(", 0, 2), (")", 0, 3), ("÷", 0, 4),
("7", 1, 0), ("8", 1, 1), ("9", 1, 2), ("×", 1, 3), ("√", 1, 4),
("4", 2, 0), ("5", 2, 1), ("6", 2, 2), ("-", 2, 3), ("x²", 2, 4),
("1", 3, 0), ("2", 3, 1), ("3", 3, 2), ("+", 3, 3), ("^", 3, 4),
("0", 4, 0), (".", 4, 1), ("=", 4, 2, 2) # "=" занимает 2 колонки
]
for btn in std_buttons:
text, r, c = btn[0], btn[1], btn[2]
colsp = btn[3] if len(btn) > 3 else 1
ttk.Button(self.btn_frame, text=text, command=lambda t=text: self._on_click(t))\
.grid(row=r, column=c, columnspan=colsp, sticky="nsew", padx=2, pady=2)
self.btn_frame.columnconfigure(c, weight=1)
# Инженерные кнопки (отображаются только в инженерном режиме)
if self.mode == "engineering":
eng_buttons = [
("sin", 5, 0), ("cos", 5, 1), ("tan", 5, 2), ("log", 5, 3), ("ln", 5, 4),
("asin", 6, 0), ("acos", 6, 1), ("atan", 6, 2), ("1/x", 6, 3), ("π", 6, 4),
("e", 7, 0), ("abs", 7, 1), ("fact", 7, 2), ("mod", 7, 3), ("%", 7, 4)
]
for btn in eng_buttons:
text, r, c = btn[0], btn[1], btn[2]
ttk.Button(self.btn_frame, text=text, command=lambda t=text: self._on_click(t))\
.grid(row=r, column=c, sticky="nsew", padx=2, pady=2)
# ==================== ЛОГИКА ==================== def _on_click(self, char):
if char == "C":
self.clear()
elif char == "⌫":
self.expression = self.expression[:-1]
self._update_display()
elif char == "=":
self.calculate()
elif char in ("×", "÷"):
self.expression += char
self._update_display()
elif char == "π":
self.expression += "pi"
self._update_display()
elif char == "e":
self.expression += "e"
self._update_display()
elif char in ("sin", "cos", "tan", "asin", "acos", "atan", "log", "ln", "sqrt", "abs", "fact"):
self.expression += f"{char}("
self._update_display()
elif char == "√":
self.expression += "sqrt("
self._update_display()
elif char == "x²":
self.expression += "**2"
self._update_display()
elif char == "1/x":
self.expression += "1/("
self._update_display()
elif char == "^":
self.expression += "**"
self._update_display()
elif char == "mod":
self.expression += "%"
self._update_display()
elif char == "%":
self.expression += "/100"
self._update_display()
else:
self.expression += char
self._update_display()
def _update_display(self):
self.display_var.set(self.expression)
def clear(self):
self.expression = ""
self._update_display()
def calculate(self): if not self.expression.strip():
return
try:
expr = self.expression.replace("×", "*").replace("÷", "/")
# Безопасное окружение для eval
safe_env = {
"pi": math.pi, "e": math.e,
"sin": self._trig_wrap(math.sin), "cos": self._trig_wrap(math.cos), "tan": self._trig_wrap(math.tan),
"asin": self._inv_trig_wrap(math.asin), "acos": self._inv_trig_wrap(math.acos), "atan": self._inv_trig_wrap(math.atan),
"sqrt": math.sqrt, "log": math.log10, "ln": math.log,
"abs": abs, "pow": pow, "fact": math.factorial
}
result = eval(expr, {"__builtins__": {}}, safe_env)
# Форматирование с учётом точности
if isinstance(result, float):
result = round(result, self.precision)
# Убираем trailing zeros, если они есть
result = f"{result:.{self.precision}f}".rstrip('0').rstrip('.')
self.expression = str(result)
self._update_display()
except Exception as e:
messagebox.showerror("Ошибка вычисления", f"Проверьте правильность выражения.\n{str(e)}")
self.expression = ""
self._update_display()
# Обёртки для тригонометрии (учёт RAD/DEG)
def _trig_wrap(self, func):
def wrapper(x):
val = float(x)
if self.angle_mode.get() == "deg":
val = math.radians(val)
return func(val)
return wrapper
def _inv_trig_wrap(self, func):
def wrapper(x):
res = func(float(x))
return math.degrees(res) if self.angle_mode.get() == "deg" else res
return wrapper
def _update_precision(self):
self.precision = self.prec_var.get()
# ==================== ПАМЯТЬ ====================
def _parse_current(self):
try: return float(self.expression) if self.expression else 0.0
except ValueError:
raise ValueError("В поле должно быть число")
def mem_store(self):
val = self._parse_current()
if len(self.memory) < self.max_memory:
self.memory.append(val)
else:
self.memory.pop(0)
self.memory.append(val)
self._update_mem_label()
messagebox.showinfo("Память", f"Сохранено: {val}")
def mem_recall(self):
if self.memory:
self.expression = str(self.memory[-1])
self._update_display()
def mem_clear(self):
self.memory.clear()
self._update_mem_label()
def _mem_op(self, op):
try:
val = self._parse_current()
if not self.memory:
self.memory.append(val)
else:
if op == "add": self.memory[-1] += val
elif op == "sub": self.memory[-1] -= val
self._update_mem_label()
except ValueError as e:
messagebox.showwarning("Ошибка памяти", str(e))
def _update_mem_label(self):
count = len(self.memory)
self.mem_label.config(text=f"[{count}/{self.max_memory}]")
self.mem_label.config(foreground="black" if count > 0 else "gray")
# ==================== МЕНЮ И КОНТЕКСТ ====================
def _create_main_menu(self):
menubar = tk.Menu(self)
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="Закрыть", command=self.destroy)
menubar.add_cascade(label="Файл", menu=file_menu)
view_menu = tk.Menu(menubar, tearoff=0)
view_menu.add_command(label="Обычный режим", command=lambda: self._set_mode("standard")) view_menu.add_command(label="Инженерный режим", command=lambda: self._set_mode("engineering"))
menubar.add_cascade(label="Вид", menu=view_menu)
help_menu = tk.Menu(menubar, tearoff=0)
help_menu.add_command(label="О программе", command=self._show_about)
menubar.add_cascade(label="Справка", menu=help_menu)
self.config(menu=menubar)
def _set_mode(self, mode):
self.mode = mode
self._build_buttons()
# Автоматическое изменение размера окна под режим
h = 560 if mode == "engineering" else 480
self.geometry(f"450x{h}")
def _show_about(self):
messagebox.showinfo("О программе",
"Калькулятор v1.0\n\n"
"Разработано в рамках Лабораторной работы №13\n"
"Дисциплина: ООП на Python\n"
"Автор: Студент группы ИБ-2\n"
"Год: 2026")
def _create_context_menu(self):
self.context_menu = tk.Menu(self, tearoff=0)
self.context_menu.add_command(label="Копировать результат", command=self._copy)
self.context_menu.add_command(label="Очистить поле", command=self.clear)
self.bind("<Button-3>", self._show_context_menu)
self.display.bind("<Button-3>", self._show_context_menu)
def _show_context_menu(self, event):
self.context_menu.tk_popup(event.x_root, event.y_root)
def _copy(self):
self.clipboard_clear()
self.clipboard_append(self.display_var.get())
if __name__ == "__main__":
app = CalculatorApp()
app.mainloop()