A function is a named block of code that does one job. You write it once and call it as many times as you like. The benefit is not really the saved typing — it is that a bug in a function is fixed in one place, and that a function with a clear input and output can be tested on its own, away from the rest of the program.
| Python | What it does |
|---|---|
def area(width, height): | Define a function with two parameters |
return width * height | Compute and send the result back |
a = area(5, 3) | Call it with arguments 5 and 3; a becomes 15 |
def greet(name="student"): | A default value, used when no argument is given |
def show(msg): print(msg) | No return — this one acts, rather than producing a value |
def area(width, height) declares two parameters; area(5, 3) supplies two arguments. Order matters: the first argument fills the first parameter.print() displays something on screen for a human. return hands a value back to the code that called the function, so it can be stored, added or passed on. A function that prints instead of returning cannot be used in a calculation: total = show_area(5, 3) sets total to None, because a function with no return returns nothing.Scope
A variable created inside a function is local: it exists only while that function runs, and the rest of the program cannot see it. This is a feature, not a limitation. It means you can use i or total inside a function without any risk of clashing with a variable of the same name elsewhere. Anything the function needs from outside should arrive as a parameter, and anything it produces should leave via return.
- Created inside a function
- Exists only while that function runs
- Invisible to the rest of the program
- Safe — cannot clash with anything outside
- Created at the top level of the file
- Readable from anywhere
- Needs the
globalkeyword to be modified inside a function - Avoid: any function might change it, which makes bugs hard to locate
The three kinds of error
| Type | When it appears | Example | How you find it |
|---|---|---|---|
| Syntax | Before the program runs | Missing colon, unbalanced bracket | Python refuses to start and names the line |
| Runtime | While running, at the moment it happens | Division by zero, IndexError, TypeError | The program crashes with a traceback |
| Logic | Never — the program runs fine | Using + where * was meant | Only by testing against known answers |
Reading a traceback
A Python traceback reads bottom-up in importance. The last line names the error type and gives a message: ZeroDivisionError: division by zero. Just above it is the line of your code that caused it, with its line number. Everything higher is the chain of calls that led there. Most students skim the whole block; reading only the last two lines usually identifies the problem immediately.
| Error message | What it usually means |
|---|---|
NameError: name 'x' is not defined | Typo in a variable name, or used before assignment |
TypeError: can only concatenate str | Mixing a string and a number — usually a missing int() |
IndexError: list index out of range | An index past the end — usually len() instead of len()-1 |
ZeroDivisionError | Dividing by a variable that turned out to be 0 — often an empty list |
IndentationError | Inconsistent indentation, often mixed tabs and spaces |
ValueError: invalid literal for int() | int() was given text that is not a number |
A method for debugging
- Reproduce it. Find the exact input that triggers the bug every time. A bug you cannot reproduce cannot be confirmed fixed.
- Read the error message properly. The last two lines of a traceback name the error type and the exact line.
- Narrow it down. Add a
print()showing the value of key variables just before the failing line, or comment out sections until the bug disappears. The last thing you removed contains it. - Trace by hand. Build a trace table with one row per loop pass. Where your table and the program disagree is where the bug lives.
- Fix one thing. Change a single thing and re-test. Changing three at once means not knowing which one mattered.
- Re-test everything. Confirm the fix works and that the inputs that used to work still do.
print(f"DEBUG i={i} total={total}") inside a loop shows you exactly what the program believes, pass by pass. It is unglamorous and it works. The moment the printed values stop matching what you expected is the moment the bug happened — and that is usually one line earlier than where the crash occurred.Testing means choosing inputs deliberately rather than trying whatever comes to mind. Use a normal value that should obviously work, a boundary value right at a limit, and an invalid value that should be rejected. For a program that grades marks out of 100, that means testing 75, then 0, 50 and 100, then −5 and 150 and the word "hello". Programs that only ever see reasonable input are the ones that fail in front of a class.