Build Calculator in Python Using Tkinter – Full Source Code

Full source code to build a calculator in Python using Tkinter. Clean structure, safe evaluation, and ready to copy for your own Python projects.
Python Tkinter calculator interface – simple GUI example

This page provides the complete source code for building a calculator in Python using Tkinter. The project includes a clean interface, safe expression handling, memory features, and a functional history system. The code is organized, easy to reuse, and suitable for developers who want a reliable Tkinter-based calculator for their Python applications. All components are included in one place so you can copy, review, or extend the functionality based on your needs.

Difficulty Level:

Beginner

requirement Libraries:

Built-in Python libraries (no installation required)
				
					"""
beautiful_calculator.py
A professional and safe scientific calculator using tkinter + AST evaluation.
Run using: python beautiful_calculator.py
"""

import tkinter as tk
from tkinter import ttk, messagebox
import math
import ast

# ----------------------------
# Safe expression evaluation
# ----------------------------
ALLOWED_NAMES = {
    "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": lambda x, base=10: math.log(x, base),
    "ln": math.log,
    "exp": math.exp,
    "abs": abs,
    "pow": pow,
}

ALLOWED_NODE_TYPES = (
    ast.Expression,
    ast.BinOp,
    ast.UnaryOp,
    ast.Num,
    ast.Constant,
    ast.Call,
    ast.Name,
    ast.Load,
    ast.Add,
    ast.Sub,
    ast.Mult,
    ast.Div,
    ast.Pow,
    ast.Mod,
    ast.FloorDiv,
    ast.USub,
    ast.UAdd,
)

def safe_eval(expr: str):
    """
    Safely evaluate a mathematical expression using AST.
    Supports: + - * / // % ** ^ functions, constants.
    """
    expr = expr.replace("^", "**")
    expr = expr.replace("×", "*").replace("÷", "/")
    expr = expr.replace(",", "")

    try:
        node = ast.parse(expr, mode="eval")
    except Exception:
        raise ValueError("Invalid expression")

    for n in ast.walk(node):
        if isinstance(n, ast.Name):
            if n.id not in ALLOWED_NAMES:
                raise ValueError(f"Unknown name: {n.id}")
        elif isinstance(n, ast.Call):
            if not isinstance(n.func, ast.Name) or n.func.id not in ALLOWED_NAMES:
                raise ValueError("Function not allowed")
        elif not isinstance(n, ALLOWED_NODE_TYPES):
            raise ValueError(f"Operation not allowed: {type(n).__name__}")

    try:
        compiled = compile(node, "", "eval")
        return eval(compiled, {"__builtins__": {}}, ALLOWED_NAMES.copy())
    except Exception as e:
        raise ValueError("Evaluation failed: " + str(e))


# ----------------------------
# GUI Calculator
# ----------------------------
class Calculator(ttk.Frame):
    def __init__(self, parent):
        super().__init__(parent, padding=12)
        self.parent = parent
        self.parent.title("Beautiful Scientific Calculator")
        self.grid(sticky="nsew")
        parent.columnconfigure(0, weight=1)
        parent.rowconfigure(0, weight=1)

        self.expr_var = tk.StringVar()
        self.history_items = []
        self.memory = 0.0

        self._build_ui()
        self._bind_keys()

    def _build_ui(self):
        display = ttk.Entry(self, textvariable=self.expr_var, font=("Segoe UI", 18), justify="left")
        display.grid(row=0, column=0, columnspan=6, sticky="nsew", pady=(0,8))
        display.focus_set()

        buttons = [
            ("MC", self.mem_clear), ("MR", self.mem_recall), ("M+", self.mem_add), ("M-", self.mem_sub), ("⌫", self.backspace), ("C", self.clear),
            ("7", lambda: self._add("7")), ("8", lambda: self._add("8")), ("9", lambda: self._add("9")), ("÷", lambda: self._add("/")), ("%", lambda: self._add("%")), ("(", lambda: self._add("(")),
            ("4", lambda: self._add("4")), ("5", lambda: self._add("5")), ("6", lambda: self._add("6")), ("×", lambda: self._add("*")), ("^", lambda: self._add("^")), (")", lambda: self._add(")")),
            ("1", lambda: self._add("1")), ("2", lambda: self._add("2")), ("3", lambda: self._add("3")), ("-", lambda: self._add("-")), ("sqrt", lambda: self._add("sqrt(")), ("log", lambda: self._add("log(")),
            ("0", lambda: self._add("0")), (".", lambda: self._add(".")), ("±", self.negate), ("+", lambda: self._add("+")), ("=", self.calculate), ("Ans", self.insert_last_result),
        ]

        r, c = 1, 0
        for (text, cmd) in buttons:
            b = ttk.Button(self, text=text, command=cmd)
            b.grid(row=r, column=c, sticky="nsew", padx=4, pady=4)
            c += 1
            if c > 5:
                c = 0
                r += 1

        for i in range(r+1):
            self.rowconfigure(i, weight=1)
        for j in range(6):
            self.columnconfigure(j, weight=1)

        self.history_box = tk.Listbox(self, height=8)
        self.history_box.grid(row=1, column=6, rowspan=5, sticky="nsew", padx=(10,0))
        self.history_box.bind("", self._use_history_item)

        hint = ttk.Label(
            self,
            text="Functions: sin(), cos(), tan(), sqrt(), ln(), log(x, base)\nUse ^ for power, % for modulo, Ans for last result",
            font=("Segoe UI", 9)
        )
        hint.grid(row=r+1, column=0, columnspan=7, sticky="w", pady=(8,0))

    def _add(self, s: str):
        self.expr_var.set(self.expr_var.get() + s)

    def clear(self):
        self.expr_var.set("")

    def backspace(self):
        self.expr_var.set(self.expr_var.get()[:-1])

    def negate(self):
        cur = self.expr_var.get()
        if cur.startswith("-"):
            self.expr_var.set(cur[1:])
        else:
            self.expr_var.set("-" + cur)

    def calculate(self):
        expr = self.expr_var.get().strip()
        if not expr:
            return
        try:
            result = safe_eval(expr)
            if isinstance(result, float) and result.is_integer():
                result = int(result)
            self._push_history(expr, result)
            self.expr_var.set(str(result))
        except Exception as e:
            messagebox.showerror("Error", str(e))

    def _push_history(self, expr, result):
        item = f"{expr} = {result}"
        self.history_items.insert(0, item)
        self.history_items = self.history_items[:50]
        self._refresh_history_box()

    def _refresh_history_box(self):
        self.history_box.delete(0, tk.END)
        for it in self.history_items:
            self.history_box.insert(tk.END, it)

    def _use_history_item(self, event=None):
        sel = self.history_box.curselection()
        if not sel:
            return
        item = self.history_box.get(sel[0])
        expr = item.split(" = ")[0]
        self.expr_var.set(expr)

    # ----------------------------
    # Memory functions
    # ----------------------------
    def mem_clear(self):
        self.memory = 0.0
        messagebox.showinfo("Memory", "Memory cleared")

    def mem_recall(self):
        self.expr_var.set(self.expr_var.get() + str(self.memory))

    def mem_add(self):
        try:
            val = safe_eval(self.expr_var.get())
            self.memory += float(val)
            messagebox.showinfo("Memory", f"Added to memory. Memory: {self.memory}")
        except Exception as e:
            messagebox.showerror("Error", "Cannot add to memory: " + str(e))

    def mem_sub(self):
        try:
            val = safe_eval(self.expr_var.get())
            self.memory -= float(val)
            messagebox.showinfo("Memory", f"Subtracted from memory. Memory: {self.memory}")
        except Exception as e:
            messagebox.showerror("Error", "Cannot subtract from memory: " + str(e))

    def insert_last_result(self):
        if self.history_items:
            last = self.history_items[0].split(" = ")[1]
            self.expr_var.set(self.expr_var.get() + last)

    # ----------------------------
    # Keyboard shortcuts
    # ----------------------------
    def _bind_keys(self):
        self.parent.bind("", lambda e: self.calculate())
        self.parent.bind("", lambda e: self.backspace())
        self.parent.bind("", lambda e: self.clear())

        for k in "0123456789+-*/().%^":
            self.parent.bind(k, lambda e, ch=k: self._add(ch))

    def _key_handler(self, event):
        pass


def main():
    root = tk.Tk()
    root.title("Beautiful Scientific Calculator")
    
    # Window size
    root.geometry("720x480")
    root.minsize(640, 420)

    # ----------------------------
    # Custom Dark Theme Styling
    # ----------------------------
    style = ttk.Style(root)
    style.theme_use("clam")

    # Main background
    root.configure(bg="#121212")

    # Entry (display) style
    style.configure(
        "TEntry",
        foreground="#00FF66",
        background="#1E1E1E",
        fieldbackground="#1E1E1E",
        padding=10,
        font=("Consolas", 22)
    )

    # Buttons
    style.configure(
        "TButton",
        font=("Segoe UI", 12, "bold"),
        foreground="white",
        background="#2A2A2A",
        padding=10,
        borderwidth=0,
        focusthickness=3,
        focuscolor="none"
    )

    # Hover effect
    style.map(
        "TButton",
        background=[("active", "#FF3B3B")],   # hover = red
        foreground=[("active", "white")]
    )

    # Listbox (history)
    root.option_add("*Listbox*Background", "#1E1E1E")
    root.option_add("*Listbox*Foreground", "#00FF66")
    root.option_add("*Listbox*Font", "Consolas 12")

    # Labels
    style.configure(
        "TLabel",
        background="#121212",
        foreground="#BBBBBB",
        font=("Segoe UI", 9)
    )

    # Frame background
    style.configure("TFrame", background="#121212")

    # ----------------------------
    # Run App
    # ----------------------------
    app = Calculator(root)
    root.mainloop()



if __name__ == "__main__":
    main()

				
			
Scroll to Top