Build calculator in Python is a classic project that helps developers enhance their skills in GUI programming, math functions, and secure code execution. In this tutorial, we will guide you step by step to build a professional and safe calculator in Python using Tkinter, featuring a dark theme, memory functions, and history tracking.
Whether you are a beginner or an intermediate Python programmer, this guide will walk you through the process step by step, explaining each part of the code. By the end, you’ll have a fully functional Python GUI calculator capable of handling complex mathematical expressions safely.
This project also demonstrates how to use AST (Abstract Syntax Tree) evaluation to prevent unsafe execution of user input—a common risk when using Python’s built-in eval() function.
Why Build a Calculator in Python?
- Practice GUI Development: Design a user-friendly interface with Tkinter.
- Work with Math Functions: Implement functions like sin, cos, tan, log, and sqrt.
- Ensure Safe Code Execution: Use AST parsing to avoid executing harmful input.
- Enhance Python Skills: Improve object-oriented programming, event handling, and lambda functions.
- Create a Reusable Tool: Use this calculator for learning, projects, or extend it into a mobile app with frameworks like Kivy.
Part 1: Importing Libraries
import tkinter as tk
from tkinter import ttk, messagebox
import math
import ast
Explanation:
tkinter: The core library for building graphical interfaces in Python.ttk: Provides themed widgets for a modern look.messagebox: Displays alerts or error messages to the user.math: Gives access to advanced mathematical functions such as pi, sin(), cos(), sqrt().ast: Allows safe evaluation of user-input expressions by analyzing the abstract syntax tree, avoiding direct use ofeval()which can be unsafe.
💡 Tip: Avoid using
eval()directly on user input. It can execute arbitrary Python code, which is a major security risk. Using AST evaluation ensures only valid mathematical expressions are executed.
Part 2: Safe Expression Evaluation with AST
Before implementing the calculator interface, we need a way to safely evaluate mathematical expressions.
Allowed Functions and Constants
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,
}
- Here, we define all constants and functions the user can use.
- Any input outside this list will trigger an error, ensuring safe evaluation.
- Functions like
logallow optional bases (default 10), andpowhandles exponentiation.
Allowed AST Node Types
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,
)
- This defines the types of operations and expressions the calculator can process.
- Includes binary operations (
+,-,*,/), unary operations (-5), function calls, and numbers.
Safe Evaluation Function
def safe_eval(expr: str):
expr = expr.replace("^", "**")
expr = expr.replace("×", "*").replace("÷", "/")
expr = expr.replace(",", "")
node = ast.parse(expr, mode="eval")
for n in ast.walk(node):
if isinstance(n, ast.Name) and n.id not in ALLOWED_NAMES:
raise ValueError(f"Unknown name: {n.id}")
elif isinstance(n, ast.Call) and (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__}")
compiled = compile(node, "<string>", "eval")
return eval(compiled, {"__builtins__": {}}, ALLOWED_NAMES.copy())
Explanation:
- Replaces common symbols (
^→**,×→*,÷→/) for Python compatibility. - Parses the input into an AST tree.
- Walks through the tree to verify every element is safe.
- Compiles and evaluates the expression using only allowed functions.
This method ensures that the calculator only executes mathematical operations, blocking any Python commands or malicious input.
Part 3: Building the 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()
- Inherits from
ttk.Framefor modern-themed widgets. expr_varholds the current input.history_itemsstores past calculations for reference.memorysupports standard calculator memory operations (MC, MR, M+, M-).
Building the Display and Buttons
display = ttk.Entry(self, textvariable=self.expr_var, font=("Segoe UI", 18), justify="right")
display.grid(row=0, column=0, columnspan=6, sticky="nsew", pady=(0,8))
- The Entry widget displays numbers and results.
- Buttons are arranged in a grid layout, including numbers, operators, functions, and memory keys.
Example Buttons:
MC, MR, M+, M-→ memory functions±→ negate number=→ calculate resultAns→ inserts the last calculation result
Implementing Basic Calculator Functions
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)
_add: Adds a digit, operator, or function to the expression.clear: Clears the display.backspace: Deletes the last character.negate: Switches the sign of the current number.
💡 Pro Tip: Using object-oriented design makes it easy to add new buttons or functions later.
Calculating and Tracking History
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))
- Uses
safe_evalfor secure computation. - Stores the expression and result in history, allowing users to reuse previous calculations.
Memory Functions
mem_clear(): Clears the memory value.mem_recall(): Inserts memory value into display.mem_add()/mem_sub(): Adds or subtracts the current value from memory.insert_last_result(): Re-inserts the last calculated value.
Keyboard Shortcuts
def _bind_keys(self):
self.parent.bind("<Return>", lambda e: self.calculate())
self.parent.bind("<BackSpace>", lambda e: self.backspace())
self.parent.bind("<Escape>", lambda e: self.clear())
for k in "0123456789+-*/().%^":
self.parent.bind(k, lambda e, ch=k: self._add(ch))
- Users can type numbers and operators directly.
- Press Enter to calculate, Backspace to delete, Escape to clear.
Part 4: Dark Theme Styling
style = ttk.Style(root)
style.theme_use("clam")
root.configure(bg="#121212")
- Applies a modern dark theme.
- Styles Entry, Buttons, Labels, and Listbox for a professional look.
- Hover effects are included for better interactivity.
Part 5: Running the Calculator
def main():
root = tk.Tk()
root.geometry("720x480")
root.minsize(640, 420)
app = Calculator(root)
root.mainloop()
- Initializes the main window and GUI.
- Runs the app loop to interact with the user.
- Execute the script using:
python beautiful_calculator.py
Conclusion
In this tutorial, we showed how to build a calculator in Python with full scientific capabilities, including:
- Safe evaluation of mathematical expressions using AST.
- A modern Tkinter GUI with a dark theme.
- Full history tracking and memory functions.
- Support for keyboard input and shortcuts.
- Handling complex math functions like sin, cos, tan, sqrt, log, and exponentiation.
This calculator project is an excellent way to practice Python GUI programming, implement safe code execution, and create a reusable tool for personal or educational purposes. You can also expand it by adding graphing features, unit conversion, or custom themes to enhance its functionality.
To explore the full working code, please visit the dedicated source code page here
