3️⃣ Functions
📖 What is a Function?
A function is a reusable block of code characterized by four components:
- A name (identifier) – cardinality: exactly 1, required
- Some parameters (inputs) – cardinality: 0 to ∞, optional
- Some instructions (executable statements) – cardinality: at least 1, required
- Some return values (output) – cardinality: 0 to ∞, optional
- The recipe has a name
- It needs ingredients: these are the parameters
- You follow the steps: these are the instructions
- You get a meal at the end : this is what’s returned
Once you have the recipe, you can make cookies any time – just give it the ingredients!
Simple Example:
def compute_mean(value1, value2):
result = (value1 + value2) / 2
return result
mean = compute_mean(2,4)
print(mean)
Explanation:
def compute_mean(value1, value2):defines a function namedcompute_meanwith two parametersresult = (value1 + value2) / 2is an instruction step which computes the average and stores it in a variable named resultreturn resultis the final instruction steps which specifies the output valuecompute_mean(2,4)executes the function with specific valuesvalues- The returned value is stored in variable
mean
🎯 Why Functions Matter
Scenario: Consider a program that calculates rectangular areas for multiple rooms. Without functions, the code becomes repetitive and difficult to maintain.
❌ Approach 1: Repetitive Code
# Living room area
length1 = 5
width1 = 4
area1 = length1 * width1
# Bedroom area
length2 = 3.5
width2 = 3
area2 = length2 * width2
# Kitchen area
length3 = 4
width3 = 3.5
area3 = length3 * width3
print(area1, area2, area3)
Identified Problems:
- The calculation
length * widthappears three times - Six variables track three calculations
- Each repetition introduces potential transcription errors
- Modifying the formula requires updating multiple locations
- Code readability deteriorates as repetitions increase
✅ Approach 2: Function-Based Code
def calculate_area(length, width):
area = length * width
return area
# Calculate areas using the function
area1 = calculate_area(5, 4)
area2 = calculate_area(3.5, 3)
area3 = calculate_area(4, 3.5)
print(area1, area2, area3)
Advantages:
- ✅ The formula exists in a single location
- ✅ Code length is reduced
- ✅ Transcription errors are eliminated
- ✅ Formula modifications require updating only one location
- ✅ Code intent is immediately clear
- ✅ The function can be reused throughout the program
⭐ The DRY Principle
Don’t Repeat Yourself
When identical or similar code appears multiple times in a program, a function should be created to encapsulate that logic. This principle is fundamental to professional software development.
Rule of thumb: If code is copied and pasted, it should be converted into a function.
🔨 Anatomy of Functions
def function_name(parameter1, parameter2):
# Instructions (must be indented by 4 spaces)
result = parameter1 + parameter2
return result
Syntactic Components:
- def – keyword initiating function definition
- function_name – identifier for the function (use descriptive names)
- (parameter1, parameter2) – parameter list (may be empty)
- : – colon concluding the function header
- Indentation – all function body statements must be indented (4 spaces)
- return – statement transmitting output value(s) to the caller and imediately ending the function execution
EXAMPLE 1: Zero parameters, one return value
def get_pi():
return 3.14159
pi_value = get_pi()
print("π ≈", pi_value)
Note: Even without parameters, parentheses () are mandatory.
EXAMPLE 2: One parameter, one return value
def square(x):
result = x ** 2
return result
number = 7
squared = square(number)
print(f"{number}² = {squared}")
Case 3: Multiple parameters, one return value
def calculate_rectangle_area(length, width):
area = length * width
return area
room1_area = calculate_rectangle_area(5, 3)
room2_area = calculate_rectangle_area(10, 4)
print("Room 1 area:", room1_area, "square units")
print("Room 2 area:", room2_area, "square units")
Execution:
calculate_rectangle_area(5, 3)is invoked- Parameter
lengthreceives value 5 - Parameter
widthreceives value 3 - Calculation:
5 * 3 = 15 - Value 15 is returned and stored in
room1_area
Case 4: Multiple parameters, multiple return values
def divide_with_remainder(dividend, divisor):
quotient = dividend // divisor
remainder = dividend % divisor
return quotient, remainder
q, r = divide_with_remainder(17, 5)
print(f"17 ÷ 5 = {q} remainder {r}")
Technical note: Multiple return values are implemented as tuples. The statement return quotient, remainder creates tuple (quotient, remainder), which is unpacked upon assignment.
✏️ Naming Convention
Function Naming Best Practices:
Effective names:
- ✅
calculate_average()– describes the operation - ✅
convert_celsius_to_fahrenheit()– clear purpose
Ineffective names:
- ❌
function1()– no semantic meaning - ❌
do_stuff()– vague purpose
Convention: Function names should use lowercase with underscores separating words (snake_case).
📤 Functions Communicate Through Return Values
⚠️ Critical Rule: Functions should RETURN results, not PRINT them
Why this matters:
When you write a function, you’re creating a tool that other parts of your program will use. Functions must give back results so they can be:
- Stored in variables
- Used in calculations
- Passed to other functions
- Tested and verified
❌ BAD – Using print inside functions:
def calculate_area(length, width):
area = length * width
print(area) # ❌ NEVER do this!
result = calculate_area(5, 3) # Prints "15" but...
print(result) # Prints "None" - the function gave nothing back!
double = result * 2 # ERROR - can't do math with None!
✅ GOOD – Using return:
def calculate_area(length, width):
area = length * width
return area # ✅ Give the value back
result = calculate_area(5, 3) # Nothing prints yet - value stored
print(result) # Now we choose when to display: 15
double = result * 2 # Works perfectly: 30
The Rule:
return= the function’s output (for the program to use)print()= displaying information to humans (for viewing only)
In this course, functions should NEVER contain print statements. The only exception is when debugging your own code temporarily.
📞 Calling Functions
⭐ Critical Distinction: Definition vs. Invocation
Definition (using def): Creates the function but does not execute it
Invocation/Call (using function name with parentheses): Executes the function’s instructions
Analogy:
- Definition = Writing a recipe in a cookbook
- Invocation = Actually preparing the meal
Merely defining a function does not execute its instructions. The function must be explicitly called.
Example: Definition vs. Invocation
# DEFINITION - creates the function
def calculate_double(x):
return x * 2
# At this point, nothing has been calculated yet
# INVOCATION - executes the function
result = calculate_double(5)
print("Result:", result)
🔗 Functions Calling Other Functions
Function Composition
Functions can invoke other functions, enabling the construction of complex programs from simpler components. This approach promotes code organization and reusability.
Principle: Decompose complex problems into smaller, manageable functions, then combine them to solve the larger problem.
This technique is called functional decomposition or modular programming.
Mathematical Function Composition
def square(x):
"""Calculate x²."""
return x ** 2
def sum_of_squares(a, b):
"""Calculate a² + b²."""
return square(a) + square(b)
import math
def distance_from_origin(x, y):
"""Calculate distance from origin: √(x² + y²)."""
sum_sq = sum_of_squares(x, y)
distance = math.sqrt(sum_sq)
return distance
point_x = 3
point_y = 4
dist = distance_from_origin(point_x, point_y)
print(f"Distance from origin to ({point_x}, {point_y}): {dist}")
Benefits of this approach:
- Each function has a single, clear responsibility
- Functions can be tested independently
- Code reusability is maximized
- Complex calculations are decomposed into comprehensible steps
Integrating Custom and Library Functions
import math
def calculate_distance(x1, y1, x2, y2):
"""Calculate Euclidean distance between two points."""
dx = x2 - x1
dy = y2 - y1
distance = math.sqrt(dx**2 + dy**2)
return distance
def calculate_circle_area(radius):
"""Calculate circle area using math.pi."""
return math.pi * radius ** 2
# Test the functions
dist = calculate_distance(0, 0, 3, 4)
print("Distance:", dist)
area = calculate_circle_area(5)
print("Circle area:", round(area, 2))
Observation: Custom functions can utilize library functions to implement complex operations efficiently.
Frequently Used Python Standard Libraries:
| Library | Purpose | Example Functions |
|---|---|---|
math |
Mathematical operations | sqrt(), sin(), pi, ceil() |
random |
Random number generation | randint(), choice(), shuffle() |
statistics |
Statistical calculations | mean(), median(), stdev() |
Note: These are standard libraries included with Python. Additional third-party libraries (such as numpy, pandas, matplotlib) require separate installation.
🔍 Tracing Function Execution
⭐ Systematic Tracing Methodology
Tracing is the process of manually executing code step-by-step to understand program behavior. For functions, this requires tracking:
- Function invocation location
- Argument values passed
- Parameter-argument mapping
- Line-by-line execution within the function
- Return value
- Continuation point after function completes
Tracing procedure:
- Locate the function call
- Identify argument values
- Navigate to function definition
- Map arguments to parameters by position
- Execute function body line by line
- Record variable values in a trace table
- Identify return value
- Continue execution from call site
Trace Example 1: Basic Function
Code:
def multiply_and_add(x, y):
product = x * y
result = product + 10
return result
a = 3
b = 4
final = multiply_and_add(a, b)
print(final)
Trace Table:
| Step | Line | Action | a | b | x | y | product | result | final |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 6 | a = 3 |
3 | — | — | — | — | — | — |
| 2 | 7 | b = 4 |
3 | 4 | — | — | — | — | — |
| 3 | 8 | Call multiply_and_add(3, 4) |
3 | 4 | — | — | — | — | — |
| 4 | 1 | Enter function; parameters created | 3 | 4 | 3 | 4 | — | — | — |
| 5 | 2 | product = 3 * 4 |
3 | 4 | 3 | 4 | 12 | — | — |
| 6 | 3 | result = 12 + 10 |
3 | 4 | 3 | 4 | 12 | 22 | — |
| 7 | 4 | return 22 |
3 | 4 | 3 | 4 | 12 | 22 | — |
| 8 | 8 | Exit function; final = 22 |
3 | 4 | — | — | — | — | 22 |
| 9 | 9 | print(22) outputs 22 |
3 | 4 | — | — | — | — | 22 |
Key observations:
- Blue rows (steps 4-7): Execution inside function;
aandbare out of scope - White rows: Execution in main program;
x,y,product,resultare out of scope - Return value (22) bridges the two scopes
⚠️ Common Errors
Error 1: Omitting Parentheses in Function Calls
def greet():
return "Hello!"
# INCORRECT - references function object
message = greet
print(message) # Outputs:
# CORRECT - parentheses invoke the function
message = greet()
print(message) # Outputs: Hello!
Solution: Always include () when invoking a function.
Error 2: Argument Count Mismatch
def add(a, b):
return a + b
# ERROR: Insufficient arguments
result = add(5) # TypeError: missing 1 required positional argument
# ERROR: Excessive arguments
result = add(5, 3, 7) # TypeError: takes 2 positional arguments but 3 were given
# CORRECT: Argument count matches parameter count
result = add(5, 3) # Works correctly
Solution: Ensure argument count matches parameter count in function definition.
Error 3: Missing Return Statement
def double(x):
result = x * 2
# Missing return statement
answer = double(5)
print(answer) # Outputs: None (not 10!)
# CORRECT version:
def double(x):
result = x * 2
return result
answer = double(5)
print(answer) # Outputs: 10
Solution: If a function should produce a value, include a return statement.
Error 4: Incorrect Indentation
# INCORRECT: Function body not indented
def calculate(x):
result = x * 2 # IndentationError
return result
# CORRECT: Function body indented by 4 spaces
def calculate(x):
result = x * 2
return result
Solution: All function body statements must be indented consistently (standard: 4 spaces).
# INCORRECT: Function call indented inside the function
def calculate(x):
result = x * 2
return result
print(calculate(2)) # WRONG! This line is indented -> Part of the function -> Will never produce a result
# CORRECT : Function call at the beginning of the line
def calculate(x):
result = x * 2
return result
print(calculate(2))
Solution: Function calls align with def, not with the function body.
Error 5: Accessing Local Variables Externally
def calculate(x):
result = x * 2
return result
answer = calculate(5)
print(answer) # Works: 10
print(result) # ERROR: NameError: name 'result' is not defined
Explanation: Variables result and x are local to the function. They do not exist in the main program scope.
Solution: Use the returned value; do not attempt to access function-internal variables.
