Scientific Calculator (Tkinter, GUI + Scientific Functions)

Python is more famous for console scripts,data science apps,web development apps,event driven,multithreaded apps – but even GUI development can be done in Python (Almost everything can be done in Python)

This chapter builds a clean-looking Scientific Calculator using Tkinter. Tkinter is one of the most used GUI (Graphical User Interface) library of Python as its open source and can be customized (upto a point)

To create a scientific calculator as outlined in the sources, the development approach focuses on building a Python-based graphical user interface (GUI) that leverages a safe evaluation mechanism to process mathematical expressions.

 Libraries Used

The application relies on two primary built-in Python libraries:

  • tkinter: This is the core library used to construct the user interface, manage the window, and handle events such as button clicks and keyboard bindings.

  • math: This library provides the underlying mathematical constants and functions required for a scientific calculator, including pi, e, trigonometric functions (sin, cos, tan), and logarithms.

 
Approach Summary

The construction of the calculator is divided into logic-based evaluation and UI management:

  1. Secure Expression Evaluation Rather than using a standard eval() function, which can be a security risk, the code implements a safe_eval helper. This function uses a whitelist called ALLOWED that restricts the execution to specific safe mathematical operations and constants provided by the math library. Before evaluation, the code performs string substitution to replace visual symbols like “×”, “÷”, and “^” with Python-compatible operators (“*”, “/”, and “**”).

 

  1. Class-Based UI Architecture The application is structured as a class named SciCalc, which inherits from tk.Tk. The layout is organised using a grid system consisting of 6 columns and 8 rows to ensure a structured alignment of buttons and the display.
  • Display: An Entry widget is used for the display area, configured with specific fonts and alignment to show user input and results.
  • Button Mapping: A comprehensive list of tuples defines each button’s label and its “kind” (e.g., “ins” for insertion, “func” for functions, “eq” for equals). This allows the programme to iterate through the list and dynamically create buttons with consistent styling and specific command bindings.

 

  1. Event Handling and Interaction The application is designed to be interactive through both mouse and keyboard:
  • Button Clicks: The on_press method determines what action to take based on the “kind” of button pressed, such as appending text to the display or triggering a calculation.
  • Keyboard Support: Specific keys are bound to methods like key_insert, allowing users to type numbers and operators directly.
  • Logical Operations: Dedicated methods handle specific calculator behaviours, such as toggle_sign (to switch between positive and negative), percent (to divide current values by 100), and clear_all.

  1. Error Handling and Final Execution The core calculation is triggered by the equals method, which attempts to evaluate the expression via safe_eval. If the expression is mathematically invalid, the application is programmed to catch the exception and display “Error” instead of crashing. Finally, the app.mainloop() command starts the event loop to keep the window active and responsive to user input


Without waiting much,we’ll get to the code 

				
					import tkinter as tk
import math

# --------- Safe evaluation helpers ----------
ALLOWED = {
    "pi": math.pi,
    "e": math.e,
    "sin": math.sin,
    "cos": math.cos,
    "tan": math.tan,
    "asin": math.asin,
    "acos": math.acos,
    "atan": math.atan,
    "sqrt": math.sqrt,
    "log": math.log10,
    "ln": math.log,
    "abs": abs,
    "round": round,
    "pow": pow,
}

def safe_eval(expr: str):
    expr = expr.replace("×", "*").replace("÷", "/").replace("^", "**")
    return eval(expr, {"__builtins__": {}}, ALLOWED)

# --------- UI App ----------
class SciCalc(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Scientific Calculator")
        self.geometry("420x520")
        self.minsize(420, 520)

        self.configure(padx=12, pady=12)

        for c in range(6):
            self.grid_columnconfigure(c, weight=1, uniform="col")
        for r in range(8):
            self.grid_rowconfigure(r, weight=1)

        display_frame = tk.Frame(self, bd=0)
        display_frame.grid(row=0, column=0, columnspan=6, sticky="nsew", pady=(0, 10))
        display_frame.grid_columnconfigure(0, weight=1)

        self.display = tk.Entry(
            display_frame,
            font=("Segoe UI", 22),
            justify="right",
            bd=0,
            highlightthickness=2,
            highlightbackground="#d0d0d0",
            highlightcolor="#8aaae5",
            relief="flat",
        )
        self.display.grid(row=0, column=0, sticky="nsew", ipady=14, padx=2)
        self.display.insert(0, "0")

        self.mode = tk.Label(self, text="SCI", font=("Segoe UI", 10))
        self.mode.grid(row=1, column=0, columnspan=6, sticky="w", pady=(0, 6))

        btns = [
            ("sin", "func"), ("cos", "func"), ("tan", "func"), ("ln", "func"), ("log", "func"), ("⌫", "back"),
            ("(", "ins"),   (")", "ins"),    ("π", "pi"),      ("e", "e"),     ("^", "ins"),    ("CE", "ce"),
            ("7", "ins"),   ("8", "ins"),    ("9", "ins"),    ("÷", "ins"),   ("√", "sqrt"),   ("C", "clear"),
            ("4", "ins"),   ("5", "ins"),    ("6", "ins"),    ("×", "ins"),   ("x²", "sq"),    ("%", "percent"),
            ("1", "ins"),   ("2", "ins"),    ("3", "ins"),    ("-", "ins"),   ("xʸ", "pow"),   ("abs", "func"),
            ("0", "ins"),   (".", "ins"),    ("±", "neg"),    ("+", "ins"),   ("=", "eq"),     ("", "noop"),
        ]

        self.btn_font = ("Segoe UI", 12)
        self.btn_font_big = ("Segoe UI", 12, "bold")

        r = 2
        idx = 0
        for rr in range(6):
            for cc in range(6):
                text, kind = btns[idx]
                idx += 1
                if kind == "noop":
                    continue

                is_eq = (text == "=")
                is_op = text in {"+", "-", "×", "÷", "^"}
                is_ctrl = text in {"C", "CE", "⌫"}

                btn = tk.Button(
                    self,
                    text=text,
                    font=self.btn_font_big if (is_eq or is_ctrl) else self.btn_font,
                    bd=0,
                    relief="flat",
                    cursor="hand2",
                    padx=6,
                    pady=10,
                    command=lambda t=text, k=kind: self.on_press(t, k),
                )

                if is_eq:
                    btn.configure(bg="#e6eefc", activebackground="#d7e5ff")
                elif is_ctrl:
                    btn.configure(bg="#f3f3f3", activebackground="#e7e7e7")
                elif is_op:
                    btn.configure(bg="#f8f8f8", activebackground="#efefef")
                elif kind in {"func", "sqrt", "sq", "pow"}:
                    btn.configure(bg="#fbfbfb", activebackground="#f0f0f0")
                else:
                    btn.configure(bg="#ffffff", activebackground="#f4f4f4")

                btn.grid(row=r + rr, column=cc, sticky="nsew", padx=5, pady=5)

        self.bind("<Return>", lambda e: self.equals())
        self.bind("<BackSpace>", lambda e: self.backspace())
        self.bind("<Escape>", lambda e: self.clear_all())

        for ch in "0123456789.+-*/()":
            self.bind(ch, self.key_insert)

    def get_text(self):
        return self.display.get()

    def set_text(self, s: str):
        self.display.delete(0, tk.END)
        self.display.insert(0, s)

    def insert(self, s: str):
        cur = self.get_text()
        if cur == "0" and s not in {".", ")", "(", "+", "-", "×", "÷", "^"}:
            self.set_text(s)
        else:
            self.display.insert(tk.END, s)

    def on_press(self, text, kind):
        if kind == "ins":
            self.insert(text)
        elif kind == "func":
            self.insert(f"{text}(")
        elif kind == "pi":
            self.insert("pi")
        elif kind == "e":
            self.insert("e")
        elif kind == "sqrt":
            self.insert("sqrt(")
        elif kind == "sq":
            self.insert("^2")
        elif kind == "pow":
            self.insert("^")
        elif kind == "percent":
            self.percent()
        elif kind == "neg":
            self.toggle_sign()
        elif kind == "back":
            self.backspace()
        elif kind == "clear":
            self.clear_all()
        elif kind == "ce":
            self.clear_entry()
        elif kind == "eq":
            self.equals()

    def key_insert(self, event):
        ch = event.char
        if ch == "*":
            self.insert("×")
        elif ch == "/":
            self.insert("÷")
        else:
            self.insert(ch)

    def clear_all(self):
        self.set_text("0")

    def clear_entry(self):
        self.set_text("0")

    def backspace(self):
        cur = self.get_text()
        if len(cur) <= 1:
            self.set_text("0")
        else:
            self.set_text(cur[:-1])

    def toggle_sign(self):
        cur = self.get_text().strip()
        if cur == "0":
            return
        if cur.startswith("-(") and cur.endswith(")"):
            self.set_text(cur[2:-1])
        elif cur.startswith("-"):
            self.set_text(cur[1:])
        else:
            self.set_text("-" + cur)

    def percent(self):
        try:
            val = safe_eval(self.get_text())
            self.set_text(str(val / 100))
        except Exception:
            self.set_text("Error")

    def equals(self):
        try:
            result = safe_eval(self.get_text())
            if isinstance(result, float):
                result = round(result, 12)
            self.set_text(str(result))
        except Exception:
            self.set_text("Error")

if __name__ == "__main__":
    app = SciCalc()
    app.mainloop()