Python Tutorial
Python Project: Calculator
A four-function calculator that parses an operator, guards divide-by-zero, and prints a clean result.
What you are building
You build a four-function calculator: add, subtract, multiply, divide. Each operation is its own function. Division checks the denominator before it runs. A short list of expressions is printed so you can see every path, including the error path.
Run the examples in the Python editor at /try. That page is a Python runner, not an HTML preview and not a C or C++ compiler. Numbers are hardcoded so Run prints immediately.
Skills used
defto name one function per operatorreturnso the caller decides how to printifto block divide-by-zerofloatvalues so 12 / 5 is not truncated- A dict that maps an operator string to a function
One function per operator
Keep the math in functions. The rest of the program only chooses which function to call. That split makes the zero check easy to find later.
Example
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
print(add(12, 5))
print(subtract(12, 5))
print(multiply(12, 5))Each call returns a number. Nothing prints inside the function. That is deliberate. A calculator that returns values can also be tested, logged, or formatted by the caller.
Guard divide-by-zero
Python will raise ZeroDivisionError if you write 12 / 0. Catch the bad input before the division. Return a clear signal the printer can understand. Here that signal is None.
Example
def divide(a, b):
if b == 0:
return None
return a / b
print(divide(12, 5))
print(divide(12, 0))
print(divide(9, 3))| Expression | Result |
|---|---|
| 12 + 5 | 17 |
| 12 - 5 | 7 |
| 12 * 5 | 60 |
| 12 / 5 | 2.4 |
| 12 / 0 | error, not a number |
Check b == 0 before you divide. Do not wait for an exception in this project. A returnedNone is enough for a printed tool. Later chapters cover try / exceptif you want the other style.
Complete program
A list of triples holds left value, operator, right value. A dict maps each operator to a function. Unknown operators get their own message. Division by zero never crashes the run.
Example
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
return None
return a / b
OPS = {'+': add, '-': subtract, '*': multiply, '/': divide}
expressions = [
(12, '+', 5),
(12, '-', 5),
(12, '*', 5),
(12, '/', 5),
(12, '/', 0),
(9, '/', 3),
(4, '%', 2),
]
print("Calculator")
print("----------")
for left, op, right in expressions:
fn = OPS.get(op)
if fn is None:
print(left, op, right, "-> unknown operator")
continue
result = fn(left, right)
if result is None:
print(left, op, right, "-> cannot divide by zero")
elif isinstance(result, float) and not result.is_integer():
print(left, op, right, "=", round(result, 4))
else:
print(left, op, right, "=", result)OPS.get(op) returns None when the key is missing, so % is handled without a KeyError. Integer-looking floats print without a trailing .0 because the branch checks is_integer().
Common mistakes
- Using
//when you wanted ordinary division.12 // 5is 2. This project uses/so 12 / 5 is 2.4. - Writing
if b = 0. That is assignment. The test is==. - Putting
printinside every math function. Then you cannot reuse the same function to fill a table or a test. - Forgetting the unknown-operator path. A typo in the expression list then raises instead of reporting.
- Mixing strings and numbers:
add("12", 5)concatenates or crashes. Keep both sides asintorfloat.
How to extend / Practice tasks
Change the complete program. Keep the zero guard intact.
- Add a
powerfunction for**and register it inOPS. Print2 ** 8as a new row. - Accept a leftover from integer division: add
remainder(a, b)for%, still returningNonewhenbis 0. - Round every printed result to two decimal places, including whole numbers such as 17.00, so the column lines up like a receipt.
📘 Real-World Deep Dive
A calculator is the classic first project because it forces the two skills every program needs: parsing messy user input into numbers, and never letting a bad input (like divide-by-zero) crash the whole thing.
What to build
A REPL-style calculator that reads "3 + 4", validates the operator, handles division by zero, and loops until the user quits.
Real-Life Example
OPS = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b,
}
def calc(expr: str) -> str:
try:
a, op, b = expr.split()
return str(OPS[op](float(a), float(b)))
except ZeroDivisionError:
return "error: divide by zero"
except (ValueError, KeyError):
return "error: type e.g. 3 + 4"
for line in ["3 + 4", "10 / 0", "6 * 7", "hi"]:
print(f"{line:8} = {calc(line)}")A dict of operators beats a long if/elif chain — adding "%" later is one line, not a new branch.
Expected Output
3 + 4 = 7.0
10 / 0 = error: divide by zero
6 * 7 = 42.0
hi = error: type e.g. 3 + 4Common mistakes
- Using
eval(expr)to "just evaluate it" is a security hole — a user can type__import__('os').system(...). Parse explicitly instead. float("hi")raisesValueError; catch it so one typo doesn't end the session.- Comparing floats with
==(e.g.0.1 + 0.2 == 0.3) isFalse. Mentionmath.isclosewhen you extend this.
🚀 Performance & Best Practices
- The dict-of-lambdas dispatch is O(1) and trivially extensible — the shape you'll reuse for command parsers and tiny interpreters.
- Keep parsing (splitting the string) separate from computing (the math) so each can be tested on its own.
- Wrap the loop's
input()in a try/except forEOFErrorso Ctrl-D exits cleanly.
🧪 Try It Yourself
- Add
%(modulo) and**(power) — one line each in the OPS dict. - Support decimals and negative numbers (
-3 * 2) and add a test for each. - Keep a running "answer" so the user can type
ans + 5to reuse the last result.