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.
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.
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()