Python Functions Fundamentals
A function is a self-contained, named block of reusable code designed to perform a single specific task. Instead of duplicating logic throughout your codebase, functions enable the DRY Principle (Don't Repeat Yourself).
How Functions Execute in Memory (The Call Stack):
When you call a function, CPython pushes a new Stack Frame containing local variable references. When the function hits a return statement, the stack frame is popped from memory and control transfers back to the caller.
# Define a reusable function to calculate final bill with tax:
def calculate_total(price, tax_rate=0.18):
"""Calculate subtotal with tax and return final bill."""
final_amount = price + (price * tax_rate)
return round(final_amount, 2)
# Calling the function multiple times with different inputs:
bill1 = calculate_total(100) # Uses default tax 18% -> 118.0
bill2 = calculate_total(500, 0.05) # Overrides tax with 5% -> 525.0
print("Order 1 Total: Rs.", bill1)
print("Order 2 Total: Rs.", bill2)
def calculate_total(price, tax_rate=0.18):creates the function object.- In
calculate_total(100),priceis bound to100andtax_ratedefaults to0.18. - The
returnstatement sends118.0back to the caller and tears down the local stack frame.
Understand the precise technical difference between parameters and arguments:
- Parameters: The variable names listed in the function definition header (e.g.
def add(x, y):). - Arguments: The concrete values passed into the function when called (e.g.
add(10, 20)).
Returning Multiple Values: In Python, a function can return multiple values separated by commas. Python automatically packs them into a single tuple, which the caller can unpack in one line:
# Function returning multiple mathematical metrics simultaneously:
def get_min_max_avg(numbers_list):
lowest = min(numbers_list)
highest = max(numbers_list)
average = sum(numbers_list) / len(numbers_list)
# Returning 3 items packs them into a tuple (lowest, highest, average):
return lowest, highest, round(average, 2)
# Unpack all 3 returned values in one clean line:
test_scores = [78, 92, 64, 88, 95, 82]
min_val, max_val, avg_val = get_min_max_avg(test_scores)
print("Scores:", test_scores)
print(f"Lowest: {min_val} | Highest: {max_val} | Class Average: {avg_val}")
Functions without an explicit return statement return None by default.
Default arguments assign fallback values to parameters if omitted by the caller. However, never use mutable objects (like lists or dictionaries) as default arguments!
In Python, default arguments are evaluated ONCE when the function is defined, not every time it is called. Using a mutable list shares that exact same list across all future calls!
# โ THE DANGEROUS MUTABLE DEFAULT BUG:
def add_item_bad(item, target_list=[]):
target_list.append(item)
return target_list
print("Bad Call 1:", add_item_bad("Apple")) # ['Apple']
print("Bad Call 2:", add_item_bad("Banana")) # ['Apple', 'Banana'] (UNINTENDED SHARED LIST!)
# โ
THE PYTHONIC SENTINEL PATTERN (SAFE):
def add_item_safe(item, target_list=None):
if target_list is None:
target_list = [] # Creates a fresh new list on every call!
target_list.append(item)
return target_list
print("\nSafe Call 1:", add_item_safe("Apple")) # ['Apple']
print("Safe Call 2:", add_item_safe("Banana")) # ['Banana'] (Clean & Isolated!)
Always use None as the default value for optional lists or dictionaries, and instantiate them inside the function body.
When invoking functions, you can pass arguments by position (in order) or explicitly by parameter name (keyword arguments):
/: Marks preceding parameters as Positional-Only (cannot be passed by name).*: Marks subsequent parameters as Keyword-Only (must be passed by name).
# Positional-only (before /) and Keyword-only (after *):
def create_user(username, email, /, *, is_admin=False, send_email=True):
return {
"username": username,
"email": email,
"is_admin": is_admin,
"send_email": send_email
}
# Calling correctly:
user1 = create_user("balaji", "balaji@test.com", is_admin=True)
print("Created User:", user1)
Keyword-only arguments force callers to write clear, self-documenting function calls (e.g. is_admin=True instead of cryptic True).
Building a robust, modular arithmetic calculator using clean pure functions:
# Modular Calculator Functions Engine
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 "โ Error: Cannot divide by zero!"
return a / b
def calculate(num1, num2, operation):
operations_map = {
"+": add,
"-": subtract,
"*": multiply,
"/": divide
}
func = operations_map.get(operation)
if func:
return func(num1, num2)
return "โ Invalid Operator!"
# Test calculator operations:
print("10 + 5 =", calculate(10, 5, "+"))
print("10 - 4 =", calculate(10, 4, "-"))
print("10 * 3 =", calculate(10, 3, "*"))
print("10 / 2 =", calculate(10, 2, "/"))
print("10 / 0 =", calculate(10, 0, "/"))
By storing functions inside a dictionary dispatch table (operations_map), we achieve $O(1)$ dispatch without ugly nested if-else ladders!
Writing def func(a=10, b): raises SyntaxError: non-default argument follows default argument. In Python, all required positional parameters MUST appear before default parameters.
Create a function convert_temperature(celsius) that returns both Fahrenheit and Kelvin in a single tuple.
def convert_temperature(celsius):
fahrenheit = (celsius * 9/5) + 32
kelvin = celsius + 273.15
return fahrenheit, kelvin
f, k = convert_temperature(25)
print("25ยฐC in Fahrenheit:", f, "ยฐF")
print("25ยฐC in Kelvin: ", k, "K")
Q What is the difference between a parameter and an argument?
Parameters are the variable placeholders listed in the function definition header. Arguments are the concrete values passed into the function during invocation.
Q Why should I never use mutable default arguments like def f(x=[])?
Default arguments are evaluated once at compile time. A mutable list is shared across all function calls, leading to data contamination bugs. Use x=None and instantiate x=[] inside the body instead.
Q What happens if a Python function does not have a return statement?
In Python, any function that finishes execution without hitting a return statement automatically returns None.