← Back to Python series
🛠
Practice Projects · ★
String parsing · Functions · Error handling

Project 3 — Calculator

Build a command-line calculator that parses infix expressions (+, -, *, /, **, %) and handles division-by-zero and invalid input gracefully.

input parsingtry/exceptfunctionseval-safe
Duration
1.5 hours
Level
📊 Basic Applied
Prerequisite
🎯 Basic Week 9
OUTCOME
A robust CLI calculator with history and safe expression evaluation

What you'll learn

  • 1Parse and evaluate arithmetic expressions
  • 2Handle errors gracefully (division by zero, invalid input)
  • 3Store and display calculation history
  • 4Support multi-step calculations (ANS reference)

Project Overview

  • Evaluate expressions like: 3 + 4 * 2, (10 - 3) ** 2
  • Commands: history, clear, quit
  • ANS refers to the previous result
  • Never use eval() — parse manually or use a safe alternative

Sample Session

text
Calculator (type 'quit' to exit)
> 3 + 4 * 2
= 11
> (10 - 3) ** 2
= 49
> ANS + 1
= 50
> 10 / 0
Error: division by zero
> history
  3 + 4 * 2 = 11
  (10 - 3) ** 2 = 49
  ANS + 1 = 50

💻 Examples

Run these examples and check the output yourself.

solution.pySafe calculator with history
CODE
import re

def safe_eval(expr, ans):
    expr = expr.replace("ANS", str(ans))
    # Allow only numbers, operators, parens, spaces
    if not re.fullmatch(r'[\d.+\-*/%(). \*\*]+', expr):
        raise ValueError("Invalid characters in expression")
    try:
        return eval(compile(expr, "<string>", "eval"),
                    {"__builtins__": {}}, {})
    except ZeroDivisionError:
        raise ZeroDivisionError("division by zero")

history, ans = [], 0
print("Calculator (type 'quit' to exit)")
while True:
    line = input("> ").strip()
    if line.lower() in ("quit", "exit"): break
    if line.lower() == "history":
        for h in history: print(f"  {h}")
        continue
    if line.lower() == "clear":
        history.clear(); ans = 0; continue
    try:
        result = safe_eval(line, ans)
        print(f"= {result}")
        history.append(f"{line} = {result}")
        ans = result
    except Exception as e:
        print(f"Error: {e}")

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Build the calculator

Goal: Implement expression evaluation, history, ANS, and error handling.

Requirements
  • Evaluate +, -, *, /, **, %
  • ANS references previous result
  • Division by zero handled gracefully
  • history command shows calculation log
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗