import math # Import the math library for safe mathematical functions import tkinter as tk # Import tkinter and give it the short name "tk" import tkinter.messagebox # Import the messagebox module (used for pop-up dialogs) from tkinter.constants import ( SUNKEN, ) # Import the SUNKEN style constant for the entry field border # ───────────────────────────────────────────────────────────── # SAFE MATH SETUP # We allow only math functions like sqrt(), sin(), cos() etc. # This prevents dangerous code from being typed into the calculator # ───────────────────────────────────────────────────────────── # Build a dictionary of safe math names from the math library # We exclude anything that starts with "__" (those are internal Python things) allowed_names = {k: v for k, v in math.__dict__.items() if not k.startswith("__")} def safe_eval(expr): """ Safely evaluate a math expression typed by the user. Instead of using Python's full eval() which can be dangerous, we restrict it to only math functions and constants. """ try: # Evaluate the expression with no built-in functions, only math names return eval(expr, {"__builtins__": {}}, allowed_names) except ZeroDivisionError: # Handle division by zero specifically return "Division by 0!" except Exception: # Handle any other invalid expression return "Error" # ───────────────────────────────────────────────────────────── # WINDOW SETUP # Everything you learned about creating a window, # adding a frame, and placing widgets goes here # ───────────────────────────────────────────────────────────── # Create the main application window win = tk.Tk() win.title("Calculator") # Set the window title # Create a Frame — a container that holds and organizes our widgets # bg sets the background color, padx adds horizontal spacing inside frame = tk.Frame(win, bg="skyblue", padx=10) frame.pack() # Add the frame to the window # ───────────────────────────────────────────────────────────── # INPUT FIELD (Entry Widget) # This is where the user sees what they are typing # and where the result is displayed after pressing "=" # ───────────────────────────────────────────────────────────── # Create an Entry widget — the text field at the top of the calculator # relief=SUNKEN gives it a pressed-in visual style # borderwidth=3 adds a border around it # width=30 sets how wide it is (in characters) # font sets the text style and size inside the field entry = tk.Entry(frame, relief=SUNKEN, borderwidth=3, width=30, font=("Arial", 16)) # Place the entry field in the grid # columnspan=4 makes it stretch across all 4 columns # ipady adds internal vertical padding to make it taller # pady adds space above and below it entry.grid(row=0, column=0, columnspan=4, ipady=5, pady=5) # ───────────────────────────────────────────────────────────── # FUNCTIONS # These are the actions that happen when buttons are clicked # Everything you learned about functions is used here # ───────────────────────────────────────────────────────────── def click(num): """ Called when a number or operator button is clicked. Inserts the button's text at the end of the entry field. tk.END means 'add it after whatever is already there' """ entry.insert(tk.END, num) def equal(): """ Called when the '=' button is clicked. Reads the expression from the entry field, calculates the result safely, then displays it. """ res = safe_eval(entry.get()) # Get what the user typed and evaluate it entry.delete(0, tk.END) # Clear the entry field completely entry.insert(0, str(res)) # Show the result starting from position 0 def clear(): """ Called when the 'Clear' button is clicked. Deletes everything in the entry field. 0 means start from the beginning, tk.END means go to the end. """ entry.delete(0, tk.END) def backspace(): """ Called when the '<-' button is clicked. Removes only the last character typed in the entry field. This works like the backspace key on a keyboard. """ current = entry.get() # Get the current text in the entry field if current: # Only do something if the field is not empty entry.delete(len(current) - 1, tk.END) # Delete the last character # ───────────────────────────────────────────────────────────── # BUTTON LAYOUT # Each button is defined as a tuple: (label, row, column) # This is a clean way to manage many buttons without # writing a separate line of code for each one # ───────────────────────────────────────────────────────────── # List of all buttons with their text, row position, and column position # Row 0 is taken by the entry field above # Buttons fill rows 1 to 6 buttons = [ ("7", 1, 0), ("8", 1, 1), ("9", 1, 2), ("/", 1, 3), # Row 1 ("4", 2, 0), ("5", 2, 1), ("6", 2, 2), ("*", 2, 3), # Row 2 ("1", 3, 0), ("2", 3, 1), ("3", 3, 2), ("-", 3, 3), # Row 3 ("0", 4, 0), (".", 4, 1), ("%", 4, 2), ("+", 4, 3), # Row 4 ("(", 5, 0), (")", 5, 1), ("<-", 5, 2), ("=", 5, 3), # Row 5 ("Clear", 6, 0), # Row 6 ] # Loop through the buttons list and create each button dynamically # This saves us from writing Button() code 21 separate times for txt, r, c in buttons: # Decide which function to connect to each button if txt == "Clear": action = clear # Clear button calls clear() elif txt == "=": action = equal # Equals button calls equal() elif txt == "<-": action = backspace # Backspace button calls backspace() else: # For all number and operator buttons, use a lambda function # lambda t=txt: click(t) captures the button's text correctly # Without t=txt, all buttons would send the last value of txt action = lambda t=txt: click(t) # Create the button and place it in the grid at the correct row and column # padx and pady add spacing between buttons # width=4 makes all buttons the same width tk.Button(frame, text=txt, padx=20, pady=20, width=4, command=action).grid( row=r, column=c, pady=2, padx=2 ) # ───────────────────────────────────────────────────────────── # KEYBOARD SHORTCUTS # Students can also type using their physical keyboard # instead of only clicking the on-screen buttons # ───────────────────────────────────────────────────────────── def key_handler(event): """ This function listens for keyboard key presses. event.keysym gives the name of the key pressed (like "Return", "Escape") event.char gives the actual character typed (like "5", "+", ".") """ if event.keysym == "Return": # Enter key works like clicking "=" equal() elif event.keysym == "Escape": # Escape key works like clicking "Clear" clear() elif event.keysym == "BackSpace": # Backspace key works like clicking "<-" backspace() elif event.char in "0123456789+-*/().%": # Any valid calculator character click(event.char) # Insert it into the entry field # Bind the key_handler function to the window # This means every key press anywhere on the window triggers key_handler win.bind("", key_handler) # ───────────────────────────────────────────────────────────── # MAIN LOOP # Start the window and keep it running. # The calculator waits here for the user to click buttons or press keys. # Nothing below this line will ever run. # ───────────────────────────────────────────────────────────── win.mainloop()