Functions
Functions are named, reusable blocks of code that perform a specific task. They are one of the most important tools in programming because they let you write code once and use it many times.
Why This Chapter Matters
Well-written functions make programs easier to read, test, and change. Almost every Python program you will write depends on functions.
- they reduce code repetition (DRY: Don't Repeat Yourself)
- they give code clear names and structure
- they let you test pieces of code in isolation
- they make programs easier to change and maintain
Defining a Function
Use the def keyword, followed by the function name, parameters in parentheses, and a colon.
def greet(name):
return f"Hello, {name}!"
message = greet("Asha")
print(message) # Hello, Asha!
Parameters and Arguments
- Parameters are the variable names in the function definition
- Arguments are the actual values passed when calling the function
def add(a, b): # a and b are parameters
return a + b
result = add(3, 7) # 3 and 7 are arguments
print(result) # 10
The return Statement
return sends a value back to the caller and ends the function immediately.
def square(n):
return n * n
print(square(5)) # 25
Without return, a function returns None:
def say_hello():
print("Hello!")
result = say_hello()
print(result) # None
A function can return multiple values as a tuple:
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([3, 1, 9, 4])
print(low, high) # 1 9
Default Parameters
Provide a default value so the argument becomes optional:
def power(base, exponent=2):
return base ** exponent
print(power(3)) # 9 (exponent defaults to 2)
print(power(3, 3)) # 27
Keyword Arguments
Call a function by naming the parameters explicitly. Order does not matter.
def describe(name, age, city):
return f"{name}, age {age}, from {city}"
print(describe(age=16, city="Mumbai", name="Leo"))
Positional-Only and Keyword-Only Arguments
def greet(name, /, *, greeting="Hello"):
# name is positional-only (before /)
# greeting is keyword-only (after *)
return f"{greeting}, {name}!"
print(greet("Asha")) # Hello, Asha!
print(greet("Asha", greeting="Hi")) # Hi, Asha!
*args — Variable Positional Arguments
Accept any number of positional arguments as a tuple:
def add_all(*numbers):
return sum(numbers)
print(add_all(1, 2, 3)) # 6
print(add_all(10, 20, 30, 40)) # 100
**kwargs — Variable Keyword Arguments
Accept any number of keyword arguments as a dictionary:
def profile(**info):
for key, value in info.items():
print(f"{key}: {value}")
profile(name="Mina", age=15, hobby="coding")
Combining All Argument Types
The order is: positional, *args, keyword-only, **kwargs:
def example(a, b, *args, keyword=True, **kwargs):
print(a, b, args, keyword, kwargs)
Scope: Local vs Global Variables
A variable created inside a function is local to that function:
def my_function():
local_var = "I only exist here"
print(local_var)
my_function()
# print(local_var) # NameError — not accessible here
A variable defined outside functions is global:
app_name = "Nandhoo"
def show_name():
print(app_name) # can read a global variable
show_name()
Use global to modify a global variable from inside a function (use sparingly):
counter = 0
def increment():
global counter
counter += 1
increment()
print(counter) # 1
Lambda Functions (Anonymous Functions)
A lambda is a small, single-expression function without a name.
square = lambda x: x ** 2
print(square(5)) # 25
# Often used with built-in functions:
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort(key=lambda x: -x) # sort descending
print(numbers) # [9, 5, 4, 3, 1, 1]
Docstrings
Document what a function does using a docstring — the first string in the body:
def calculate_area(radius):
"""
Calculate the area of a circle.
Args:
radius (float): The radius of the circle.
Returns:
float: The area of the circle.
"""
import math
return math.pi * radius ** 2
Access it with help(calculate_area) or calculate_area.__doc__.
Higher-Order Functions
Functions can accept other functions as arguments or return them.
map() — Transform Each Item
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
print(doubled) # [2, 4, 6, 8, 10]
filter() — Keep Items That Match
numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6]
Returning a Function
def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
triple = make_multiplier(3)
print(triple(10)) # 30
Recursion
A function that calls itself is recursive. It must have a base case to stop.
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
Common Mistakes
- forgetting
return(function returnsNonesilently) - using mutable default arguments like
def func(items=[])— useNoneinstead - modifying a global variable without
globalkeyword (causesUnboundLocalError) - very long functions with too many responsibilities (prefer small, focused functions)
Mini Exercises
- Write a function
is_even(n)that returnsTrueifnis even. - Write a function
celsius_to_fahrenheit(c)with the formula(c * 9/5) + 32. - Write a function using
*argsthat returns the average of all arguments. - Write a function that takes another function as an argument and calls it.
- Write a recursive function to compute the nth Fibonacci number.
Review Questions
- What is the difference between a parameter and an argument?
- What does a function return when it has no
returnstatement? - What is the difference between
*argsand**kwargs? - When would you use a lambda function instead of a regular function?
- What is a docstring and why should you write one?
Reference Checklist
- I can define functions with
defand call them - I understand positional, keyword, default, *args, and **kwargs
- I understand local vs global scope
- I can write and use lambda functions
- I can write recursive functions with a base case
- I can write proper docstrings