Варианты калькулятора на tkinter: от базового до продвинутого

Раздел: Библиотеки -> Графический интерфейс

Калькулятор на tkinter: варианты реализации

Основное решение представляет собой простой калькулятор с графическим интерфейсом на tkinter, поддерживающий четыре базовых арифметических действия. Используется функция eval для вычисления введённого выражения. Несмотря на удобство, такой подход небезопасен для приложений, работающих с пользовательским вводом из непроверенных источников. Для учебных проектов и прототипов этого обычно достаточно.

import tkinter as tk

def calculate():
    try:
        result = eval(entry.get())
        label.config(text="Результат: " + str(result))
    except Exception as e:
        label.config(text="Ошибка: " + str(e))

root = tk.Tk()
root.title("Простой калькулятор")

entry = tk.Entry(root, width=30)
entry.pack(pady=10)

btn = tk.Button(root, text="Вычислить", command=calculate)
btn.pack()

label = tk.Label(root, text="Введите выражение")
label.pack()

root.mainloop()

вывод окна python (создание окна с выводом в python)

Пользователь вводит строку, например 2+3*4, нажимает кнопку, и программа выводит результат. При ошибках (деление на ноль, неверный синтаксис) выводится сообщение.

Типичные проблемы:

  • Использование eval позволяет выполнять произвольный код, что может быть опасно при работе с внешними данными.
  • Отсутствует проверка корректности выражения: ввод букв приводит к ошибке NameError.
  • Нет возможности очистить поле ввода после расчёта.

Цель использования: Быстрая демонстрация возможностей tkinter и базового взаимодействия с элементами управления. Подходит для обучения или личного использования, когда вопросы безопасности не критичны.

Как сделать калькулятор с кнопками для каждого символа, а не только с полем ввода?

Решение с кнопками даёт более привычный интерфейс. Каждое нажатие добавляет символ в поле ввода.

import tkinter as tk

def press(key):
    entry.insert(tk.END, key)

def equal():
    try:
        result = eval(entry.get())
        entry.delete(0, tk.END)
        entry.insert(tk.END, str(result))
    except:
        entry.delete(0, tk.END)
        entry.insert(tk.END, "Ошибка")

def clear():
    entry.delete(0, tk.END)

root = tk.Tk()
root.title("Калькулятор с кнопками")

entry = tk.Entry(root, width=30, borderwidth=5)
entry.grid(row=0, column=0, columnspan=4, padx=10, pady=10)

buttons = [
    ('1',1,0), ('2',1,1), ('3',1,2), ('+',1,3),
    ('4',2,0), ('5',2,1), ('6',2,2), ('-',2,3),
    ('7',3,0), ('8',3,1), ('9',3,2), ('*',3,3),
    ('C',4,0), ('0',4,1), ('=',4,2), ('/',4,3)
]

for (text, row, col) in buttons:
    if text == '=':
        btn = tk.Button(root, text=text, command=equal, width=5)
    elif text == 'C':
        btn = tk.Button(root, text=text, command=clear, width=5)
    else:
        btn = tk.Button(root, text=text, command=lambda t=text: press(t), width=5)
    btn.grid(row=row, column=col, padx=2, pady=2)

root.mainloop()

открыть окно python (открыть окно на python)

Теперь пользователь вводит выражение, нажимая кнопки. Кнопка = вычисляет результат, C очищает поле.

Проблемы:

  • Всё так же используется eval, что небезопасно.
  • После вычисления результат отображается в поле ввода, что может быть неудобно для редактирования.

Цель: Создание интуитивно понятного интерфейса, напоминающего настольные калькуляторы. Подходит для пользователей, предпочитающих ввод мышью.

Как реализовать безопасный калькулятор без eval?

Вместо eval можно написать парсер, который разбирает строку и вычисляет значение по правилам математики. Ниже приведён упрощённый алгоритм с использованием обратной польской записи (ОПЗ).

import operator, re

def to_postfix(expression):
    """Преобразует инфиксное выражение в ОПЗ"""
    precedence = {'+':1, '-':1, '*':2, '/':2}
    output = []
    stack = []
    tokens = re.findall(r'\d+\.?\d*|[+\-*/()]', expression)
    for token in tokens:
        if token.isdigit() or '.' in token:
            output.append(token)
        elif token == '(':
            stack.append(token)
        elif token == ')':
            while stack and stack[-1] != '(':
                output.append(stack.pop())
            stack.pop()
        else:  # оператор
            while stack and stack[-1] != '(' and precedence.get(stack[-1], 0) >= precedence[token]:
                output.append(stack.pop())
            stack.append(token)
    while stack:
        output.append(stack.pop())
    return output

def evaluate_postfix(postfix):
    """Вычисляет значение постфиксного выражения"""
    ops = {
        '+': operator.add,
        '-': operator.sub,
        '*': operator.mul,
        '/': operator.truediv
    }
    stack = []
    for token in postfix:
        if token in ops:
            b = stack.pop()
            a = stack.pop()
            stack.append(ops[token](a, b))
        else:
            stack.append(float(token))
    return stack[0]

def safe_calc(expression):
    # удалим все лишние пробелы и проверим на недопустимые символы
    expression = expression.replace(' ', '')
    if not re.match(r'^[\d+\-*/().]+$', expression):
        raise ValueError("Недопустимые символы")
    postfix = to_postfix(expression)
    return evaluate_postfix(postfix)

# Пример использования
print(safe_calc("2+3*4"))  # 14.0

Python окно (создание окон в python tkinter)

В tkinter остаётся только заменить вызов eval на вызов safe_calc. Это исключает выполнение произвольного кода.

Проблемы:

  • Парсер не обрабатывает унарный минус (например, -5+3).
  • Не поддерживаются скобки вложенной глубины более одного уровня? Нет, поддерживаются.
  • Требуется дополнительная обработка десятичных разделителей (запятая вместо точки).

Цель: Безопасный для внешнего ввода калькулятор. Применяется в проектах, где пользователь может вводить данные произвольного вида.

Как добавить функции научного калькулятора (тригонометрия, возведение в степень)?

Расширить функциональность можно, либо используя eval с добавлением допустимых функций (sin, cos, log и т.д.), либо написав расширенный парсер. Если не требуется высокая безопасность, удобно использовать eval с ограниченной областью видимости.

import math
def safe_eval(expression):
    allowed_names = {
        k: v for k, v in math.__dict__.items() if not k.startswith('__')
    }
    allowed_names["abs"] = abs
    # можно добавить свои функции
    try:
        return eval(expression, {"__builtins__": {}}, allowed_names)
    except Exception as e:
        return f"Ошибка: {e}"

# Примеры
print(safe_eval("sin(pi/2)"))  # 1.0
print(safe_eval("2**10"))      # 1024

Python tkinter canvas (холст canvas в tkinter)

Интерфейс tkinter можно дополнить кнопками для функций: sin, cos, log, sqrt и т.д. При нажатии они вставляют соответствующую строку, например sin(.

Проблемы:

  • Использование eval может быть подвержено атакам, если злоумышленник найдёт способ обойти ограничения.
  • Необходимость экранировать специальные символы (например, ** для возведения в степень нужно заменять на ^ и наоборот).

Цель: Инженерные расчёты. Подходит для студентов и специалистов, которым нужны расширенные математические операции.

Как сделать калькулятор с сохранением истории вычислений?

Добавляется текстовый виджет или Listbox, куждая записывается каждое выполненное выражение и результат. При запуске история может загружаться из файла.

import tkinter as tk
from tkinter import scrolledtext

history = []

def calculate():
    expr = entry.get()
    try:
        result = eval(expr)
        history.append(f"{expr} = {result}")
        history_box.insert(tk.END, history[-1])
        entry.delete(0, tk.END)
        label.config(text=f"Результат: {result}")
    except Exception as e:
        label.config(text=f"Ошибка: {e}")

root = tk.Tk()
root.title("Калькулятор с историей")

entry = tk.Entry(root, width=30)
entry.pack(pady=5)

btn = tk.Button(root, text="Вычислить", command=calculate)
btn.pack()

label = tk.Label(root, text="")
label.pack()

history_box = scrolledtext.ScrolledText(root, width=40, height=10)
history_box.pack(pady=5)

root.mainloop()

При каждом нажатии кнопки запись добавляется в список history и в виджет. Можно добавить кнопку очистки истории или сохранения в файл.

Проблемы:

  • История хранится только в памяти; при закрытии программы теряется.
  • Длинная история может замедлить работу интерфейса при большом количестве записей.

Цель: Анализ предыдущих вычислений. Удобно при повторяющихся расчётах или для проверки последовательности операций.

- Tkinter python ввод (ввод данных в tkinter)
- Python tkinter виджет (виджеты tkinter)
- Python file select (диалог выбора файла в python (tkinter.filedialog))

Расширенные примеры реализации калькулятора

Пример 1: Калькулятор с поддержкой клавиатурного ввода

Пользователь может набирать выражение на клавиатуре, а нажатие Enter запускает расчёт. Дополнительно привязываются другие клавиши: Escape для очистки.

Пример
import tkinter as tk

def calculate(event=None):
    try:
        result = eval(entry.get())
        entry.delete(0, tk.END)
        entry.insert(0, str(result))
    except:
        entry.delete(0, tk.END)
        entry.insert(0, "Ошибка")

def clear(event=None):
    entry.delete(0, tk.END)

root = tk.Tk()
root.title("Калькулятор с клавиатурой")

entry = tk.Entry(root, width=30)
entry.pack()
entry.bind('', calculate)
entry.bind('', clear)

root.mainloop()
Окно с полем ввода. После ввода 45+55 и нажатия Enter поле показывает 100. После Escape поле очищается.

Пример 2: Калькулятор с изменяемой цветовой схемой (темизация)

Используется библиотека ttkbootstrap для современных тем. После установки (pip install ttkbootstrap) можно быстро переключить внешний вид.

Пример
import tkinter as tk
from ttkbootstrap import Style

style = Style(theme='darkly')  # или 'flatly', 'superhero', 'minty'
root = style.master
root.title("Тёмный калькулятор")

entry = tk.Entry(root, width=30, font=('Arial', 14))
entry.pack(pady=10)

def calc():
    try:
        result = eval(entry.get())
        entry.delete(0, tk.END)
        entry.insert(0, str(result))
    except:
        entry.delete(0, tk.END)
        entry.insert(0, "Ошибка")

btn = tk.Button(root, text="Вычислить", command=calc, bootstyle='success')
btn.pack()

root.mainloop()
Окно с тёмной темой: тёмный фон, зелёная кнопка, светлый текст в поле ввода. Внешний вид можно изменить простой заменой параметра theme.

Пример 3: Калькулятор с дробными числами и десятичным разделителем

Поддержка точек и запятых. Внутренне запятая заменяется на точку для корректного вычисления.

Пример
import tkinter as tk

def calculate(event=None):
    expr = entry.get().replace(',', '.')
    try:
        result = eval(expr)
        result_str = str(result).replace('.', ',')
        entry.delete(0, tk.END)
        entry.insert(0, result_str)
    except:
        entry.delete(0, tk.END)
        entry.insert(0, "Ошибка")

root = tk.Tk()
root.title("Калькулятор с запятой")

entry = tk.Entry(root, width=30)
entry.pack()
entry.bind('', calculate)

root.mainloop()
После ввода 2,5+3,5 и нажатия Enter отображается 6,0.

Пример 4: Калькулятор с отображением пошагового решения (для демонстрации)

Показывает промежуточные результаты при нажатии каждой кнопки операции. Используется список и временные переменные.

Пример
import tkinter as tk

class StepCalc:
    def __init__(self, root):
        self.root = root
        self.root.title("Пошаговый калькулятор")
        self.total = 0
        self.current = ''
        self.operation = None
        self.steps = []

        self.entry = tk.Entry(root, width=30)
        self.entry.grid(row=0, column=0, columnspan=4)

        self.steps_text = tk.Text(root, height=5, width=40)
        self.steps_text.grid(row=1, column=0, columnspan=4)

        buttons = [
            ('1',1,0), ('2',1,1), ('3',1,2), ('+',1,3),
            ('4',2,0), ('5',2,1), ('6',2,2), ('-',2,3),
            ('7',3,0), ('8',3,1), ('9',3,2), ('*',3,3),
            ('C',4,0), ('0',4,1), ('=',4,2), ('/',4,3)
        ]
        for (text, row, col) in buttons:
            if text == '=':
                btn = tk.Button(root, text=text, command=self.result)
            elif text == 'C':
                btn = tk.Button(root, text=text, command=self.clear)
            else:
                btn = tk.Button(root, text=text, command=lambda t=text: self.click(t))
            btn.grid(row=row, column=col, padx=2, pady=2)

    def click(self, key):
        if key.isdigit() or key == '.':
            self.entry.insert(tk.END, key)
        else:
            self.current = self.entry.get()
            if self.current:
                self.steps.append(f"Текущее: {self.current}")
            self.operation = key
            self.entry.delete(0, tk.END)
            self.steps.append(f"Операция: {key}")
            self.update_steps()

    def result(self):
        self.current = self.entry.get()
        if self.current and self.operation:
            try:
                expr = f"{self.total} {self.operation} {self.current}"
                self.total = eval(expr)
                self.steps.append(f"Результат: {expr} = {self.total}")
                self.entry.delete(0, tk.END)
                self.entry.insert(0, str(self.total))
                self.operation = None
                self.update_steps()
            except:
                self.entry.delete(0, tk.END)
                self.entry.insert(0, "Ошибка")

    def clear(self):
        self.total = 0
        self.current = ''
        self.operation = None
        self.steps.clear()
        self.entry.delete(0, tk.END)
        self.update_steps()

    def update_steps(self):
        self.steps_text.delete('1.0', tk.END)
        for step in self.steps[-5:]:  # показываем последние 5 шагов
            self.steps_text.insert(tk.END, step + '\n')

root = tk.Tk()
app = StepCalc(root)
root.mainloop()
Пользователь вводит 5, нажимает +, вводит 3, нажимает =. В текстовом поле появляются шаги: "Текущее: 5", "Операция: +", "Результат: 5 + 3 = 8".

Калькулятор на tkinter - comments

En
Python калькулятор tkinter (python)