Module 11: Functions in Python
Functions are one of the most important concepts in Python programming. A function is a reusable block of code designed to perform a specific task.
Instead of writing the same code repeatedly, you can define the logic once inside a function and call it whenever you need it.
For example, instead of writing separate code to calculate the square of every number, you can create one function:
1def square(number): 2 return number * number 3 4print(square(5)) 5print(square(10))
Output:
125 2100
Functions help make Python programs:
- Reusable
- Organized
- Easier to read
- Easier to test
- Easier to debug
- Easier to maintain
By the end of this module, you will understand how to define and call functions, use parameters and arguments, return values, use default and keyword arguments, work with *args and **kwargs, create lambda functions, understand variable scope, use nested functions, and implement recursion.
What Is a Function?
A function is a named block of code that performs a particular task.
You can think of a function as a small machine:
1Input → Processing → Output
For example:
1def greet(): 2 print("Welcome to Python!") 3 4greet()
Output:
1Welcome to Python!
The function is defined using the def keyword.
The function is executed when you call it:
1greet()
Why Use Functions?
Without functions, repeated code can make a program difficult to maintain.
For example:
1print("Hello Ankit") 2print("Welcome to Python") 3 4print("Hello Rahul") 5print("Welcome to Python") 6 7print("Hello Aman") 8print("Welcome to Python")
The repeated logic can be placed inside a function:
1def welcome(name): 2 print(f"Hello {name}") 3 print("Welcome to Python") 4 5welcome("Ankit") 6welcome("Rahul") 7welcome("Aman")
Output:
1Hello Ankit 2Welcome to Python 3Hello Rahul 4Welcome to Python 5Hello Aman 6Welcome to Python
If you later change the welcome message, you only need to modify the function.
Function Syntax
The general syntax of a Python function is:
1def function_name(parameters): 2 # Function body 3 return value
A function does not always need parameters or a return statement.
For example:
1def say_hello(): 2 print("Hello, Python!") 3 4say_hello()
Defining and Calling a Function
There are two important steps:
- Define the function.
- Call the function.
Example:
1def greet(): 2 print("Good morning!") 3 4greet() 5greet()
Output:
1Good morning! 2Good morning!
The function can be called multiple times.
Function With Multiple Statements
A function can contain several statements.
1def show_profile(): 2 name = "Ankit" 3 language = "Python" 4 5 print("Name:", name) 6 print("Language:", language) 7 8show_profile()
Output:
1Name: Ankit 2Language: Python
All statements belonging to the function must be properly indented.
Parameters and Arguments
Parameters and arguments are related but have different meanings.
Parameter
A parameter is a variable defined in the function declaration.
1def greet(name): 2 print(f"Hello, {name}")
Here, name is a parameter.
Argument
An argument is the actual value passed when calling the function.
1greet("Ankit")
Here, "Ankit" is an argument.
So:
1Parameter → Variable in function definition 2Argument → Actual value passed to function
Function With One Parameter
1def greet(name): 2 print(f"Hello, {name}!") 3 4greet("Ankit") 5greet("Rahul")
Output:
1Hello, Ankit! 2Hello, Rahul!
The same function can work with different values.
Function With Multiple Parameters
A function can accept multiple parameters.
1def add(a, b): 2 return a + b 3 4result = add(10, 20) 5 6print(result)
Output:
130
Another example:
1def calculate_area(length, width): 2 return length * width 3 4area = calculate_area(10, 5) 5 6print("Area:", area)
Output:
1Area: 50
Positional Arguments
Positional arguments are assigned to parameters according to their position.
1def divide(a, b): 2 return a / b 3 4print(divide(20, 5))
Output:
14.0
Here:
1a = 20 2b = 5
Changing the order changes the result:
1print(divide(5, 20))
Output:
10.25
Keyword Arguments
Keyword arguments explicitly specify the parameter name.
1def student(name, age, city): 2 print(f"Name: {name}") 3 print(f"Age: {age}") 4 print(f"City: {city}") 5 6student( 7 city="Delhi", 8 name="Ankit", 9 age=22 10)
Output:
1Name: Ankit 2Age: 22 3City: Delhi
Keyword arguments can make function calls easier to understand, especially when a function has several parameters.
Mixing Positional and Keyword Arguments
You can combine positional and keyword arguments.
1def employee(name, age, department): 2 print(name, age, department) 3 4employee("Ankit", age=22, department="Engineering")
However, positional arguments must come before keyword arguments.
Correct:
1employee("Ankit", age=22, department="Engineering")
Incorrect:
1employee(name="Ankit", 22, department="Engineering")
The second example causes a syntax error.
Default Arguments
A parameter can have a default value.
1def greet(name="Guest"): 2 print(f"Hello, {name}!") 3 4greet() 5greet("Ankit")
Output:
1Hello, Guest! 2Hello, Ankit!
If no argument is provided, "Guest" is used.
Practical Example With Default Arguments
1def power(base, exponent=2): 2 return base ** exponent 3 4print(power(5)) 5print(power(5, 3))
Output:
125 2125
In the first call, exponent uses its default value of 2.
Important Rule for Default Parameters
Parameters without defaults should generally come before parameters with defaults.
Correct:
1def create_user(name, role="user"): 2 print(name, role)
Incorrect:
1def create_user(role="user", name): 2 print(name, role)
Python does not allow a required parameter to follow a default parameter.
The return Statement
The return statement sends a value from a function back to the code that called it.
Example:
1def add(a, b): 2 return a + b 3 4result = add(5, 10) 5 6print(result)
Output:
115
The returned value can be stored in a variable:
1result = add(5, 10)
It can also be used directly:
1print(add(5, 10))
print() vs return
This is an important distinction.
Using print():
1def add(a, b): 2 print(a + b)
Using return:
1def add(a, b): 2 return a + b
The return version is more reusable because the returned value can be stored, compared, or used in another calculation.
1result = add(10, 20) 2 3if result > 25: 4 print("Large result")
A function that only prints the result cannot be used in the same way.
Returning Multiple Values
Python allows a function to return multiple values.
1def calculate(a, b): 2 return a + b, a - b 3 4total, difference = calculate(10, 5) 5 6print("Sum:", total) 7print("Difference:", difference)
Output:
1Sum: 15 2Difference: 5
Technically, Python returns these values as a tuple:
1def calculate(a, b): 2 return a + b, a - b
is equivalent to returning:
1return (a + b, a - b)
Returning Early
A return statement immediately ends the function.
1def check_age(age): 2 if age < 0: 3 return "Invalid age" 4 5 if age >= 18: 6 return "Adult" 7 8 return "Minor" 9 10print(check_age(20)) 11print(check_age(15)) 12print(check_age(-2))
Output:
1Adult 2Minor 3Invalid age
Early returns can make functions easier to read by handling invalid or special cases first.
What Happens When a Function Has No return?
If a function does not explicitly return a value, Python returns None.
1def greet(): 2 print("Hello") 3 4result = greet() 5 6print("Returned value:", result)
Output:
1Hello 2Returned value: None
This is different from returning a value such as:
1return 0
Variable-Length Arguments
Sometimes you don't know how many arguments a function will receive.
Python provides:
1*args 2**kwargs
*args
*args allows a function to accept any number of positional arguments.
1def total(*numbers): 2 return sum(numbers) 3 4print(total(1, 2)) 5print(total(1, 2, 3)) 6print(total(1, 2, 3, 4, 5))
Output:
13 26 315
Inside the function, numbers is a tuple.
1def show_values(*values): 2 print(values) 3 4show_values(10, 20, 30)
Output:
1(10, 20, 30)
Using *args in a Loop
1def display_names(*names): 2 for name in names: 3 print(name) 4 5display_names("Ankit", "Rahul", "Aman")
Output:
1Ankit 2Rahul 3Aman
**kwargs
**kwargs allows a function to accept any number of keyword arguments.
Inside the function, the values are stored in a dictionary.
1def show_details(**details): 2 for key, value in details.items(): 3 print(f"{key}: {value}") 4 5show_details( 6 name="Ankit", 7 age=22, 8 city="Delhi" 9)
Output:
1name: Ankit 2age: 22 3city: Delhi
Combining Normal Parameters, *args, and **kwargs
Python allows these forms to be combined.
1def profile(name, *skills, **details): 2 print("Name:", name) 3 print("Skills:", skills) 4 print("Details:", details) 5 6profile( 7 "Ankit", 8 "Python", 9 "Django", 10 city="Delhi", 11 experience=2 12)
Output:
1Name: Ankit 2Skills: ('Python', 'Django') 3Details: {'city': 'Delhi', 'experience': 2}
The order in a function definition matters.
Lambda Functions
A lambda function is a small anonymous function created with the lambda keyword.
Syntax
1lambda arguments: expression
Example:
1square = lambda number: number * number 2 3print(square(5))
Output:
125
A lambda function can contain only an expression, not a normal multi-statement function body.
Lambda With Multiple Arguments
1add = lambda a, b: a + b 2 3print(add(10, 20))
Output:
130
For larger or reusable logic, a normal def function is usually more readable.
Lambda With sorted()
Lambda functions are commonly useful as small key functions.
1students = [ 2 ("Rahul", 80), 3 ("Ankit", 95), 4 ("Aman", 85) 5] 6 7students.sort(key=lambda student: student[1]) 8 9print(students)
Output:
1[('Rahul', 80), ('Aman', 85), ('Ankit', 95)]
The lambda tells sort() to use the student's marks as the sorting key.
Lambda With max()
1students = [ 2 ("Rahul", 80), 3 ("Ankit", 95), 4 ("Aman", 85) 5] 6 7top_student = max(students, key=lambda student: student[1]) 8 9print(top_student)
Output:
1('Ankit', 95)
Variable Scope
Scope determines where a variable can be accessed.
The two most important scopes for beginners are:
- Local scope
- Global scope
Python also has enclosing and built-in scopes, which are important when learning advanced function behavior.
Local Variables
A variable created inside a function normally has local scope.
1def calculate(): 2 number = 100 3 print(number) 4 5calculate()
Output:
1100
The variable number cannot normally be accessed outside the function:
1def calculate(): 2 number = 100 3 4calculate() 5 6print(number)
This results in:
1NameError
because number exists only inside calculate().
Global Variables
A variable defined outside a function is a global variable.
1message = "Python" 2 3def show_message(): 4 print(message) 5 6show_message()
Output:
1Python
The function can read the global variable because there is no local variable named message.
Local and Global Variables With the Same Name
If a local variable has the same name as a global variable, the local variable takes precedence inside the function.
1name = "Global" 2 3def show_name(): 4 name = "Local" 5 print(name) 6 7show_name() 8 9print(name)
Output:
1Local 2Global
The two variables are separate.
The global Keyword
The global keyword allows a function to modify a global variable.
1count = 10 2 3def update_count(): 4 global count 5 count = 20 6 7update_count() 8 9print(count)
Output:
120
Although global is valid, modifying global state unnecessarily can make programs harder to understand and test.
A better approach is often to return the new value:
1def update_count(count): 2 return count + 10 3 4count = update_count(count) 5 6print(count)
This approach keeps the function easier to reason about.
Nested Functions
A nested function is a function defined inside another function.
1def outer(): 2 print("Outer function") 3 4 def inner(): 5 print("Inner function") 6 7 inner() 8 9outer()
Output:
1Outer function 2Inner function
The inner() function is available inside outer() but is not directly available outside it.
Returning a Nested Function
A function can return another function.
1def outer(): 2 def inner(): 3 print("Hello from inner function") 4 5 return inner 6 7message = outer() 8 9message()
Output:
1Hello from inner function
This concept is important for understanding closures and decorators.
Closures
A closure occurs when an inner function remembers values from its enclosing scope.
1def multiplier(factor): 2 def multiply(number): 3 return number * factor 4 5 return multiply 6 7double = multiplier(2) 8triple = multiplier(3) 9 10print(double(10)) 11print(triple(10))
Output:
120 230
The returned functions remember the factor value from the outer function.
Recursive Functions
A recursive function is a function that calls itself.
A recursive function needs a base case to stop the recursion.
For example:
1def countdown(number): 2 if number == 0: 3 return 4 5 print(number) 6 countdown(number - 1) 7 8countdown(5)
Output:
15 24 33 42 51
The base case is:
1if number == 0: 2 return
Without a base case, the function would continue calling itself until Python raises a recursion-related error.
Factorial Using Recursion
The factorial of n is:
1n! = n × (n - 1) × ... × 1
For example:
15! = 5 × 4 × 3 × 2 × 1 = 120
Python implementation:
1def factorial(number): 2 if number <= 1: 3 return 1 4 5 return number * factorial(number - 1) 6 7print(factorial(5))
Output:
1120
Recursive vs Iterative Solutions
The factorial can also be calculated using a loop:
1def factorial(number): 2 result = 1 3 4 for value in range(2, number + 1): 5 result *= value 6 7 return result 8 9print(factorial(5))
For simple calculations like factorial, the iterative approach is generally more memory-efficient and avoids recursion-depth limitations.
Recursion is particularly useful when a problem naturally consists of smaller versions of the same problem, such as traversing certain tree structures.
Practice Project: BMI Calculator
A function can separate calculation logic from user interaction.
1def calculate_bmi(weight, height): 2 if weight <= 0 or height <= 0: 3 raise ValueError("Weight and height must be positive.") 4 5 return weight / (height ** 2) 6 7 8weight = float(input("Enter weight in kg: ")) 9height = float(input("Enter height in meters: ")) 10 11bmi = calculate_bmi(weight, height) 12 13print(f"BMI: {bmi:.2f}")
Example:
1Enter weight in kg: 70 2Enter height in meters: 1.75 3BMI: 22.86
You can separate classification into another function:
1def bmi_category(bmi): 2 if bmi < 18.5: 3 return "Underweight" 4 elif bmi < 25: 5 return "Normal" 6 elif bmi < 30: 7 return "Overweight" 8 else: 9 return "Obese" 10 11 12print(bmi_category(22.86))
Output:
1Normal
Keeping calculation and classification in separate functions makes the program easier to test and reuse.
Practice Project: Factorial Calculator
1def factorial(number): 2 if number < 0: 3 raise ValueError("Factorial is not defined for negative numbers.") 4 5 result = 1 6 7 for value in range(2, number + 1): 8 result *= value 9 10 return result 11 12 13number = int(input("Enter a non-negative integer: ")) 14 15try: 16 print("Factorial:", factorial(number)) 17except ValueError as error: 18 print("Error:", error)
This example demonstrates:
- Function definition
- Parameters
- Return values
- Loops
- Validation
- Exceptions
Practice Project: Fibonacci Sequence
A function can generate a Fibonacci sequence efficiently using iteration.
1def fibonacci(count): 2 if count < 0: 3 raise ValueError("Count cannot be negative.") 4 5 sequence = [] 6 first, second = 0, 1 7 8 for _ in range(count): 9 sequence.append(first) 10 first, second = second, first + second 11 12 return sequence 13 14 15print(fibonacci(10))
Output:
1[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
The iterative implementation is preferable to naive recursive Fibonacci for larger values because the naive recursive version repeats many calculations.
Practice Project: Prime Number Checker
Create a reusable function that determines whether a number is prime.
1def is_prime(number): 2 if number < 2: 3 return False 4 5 for divisor in range(2, int(number ** 0.5) + 1): 6 if number % divisor == 0: 7 return False 8 9 return True 10 11 12print(is_prime(17)) 13print(is_prime(20))
Output:
1True 2False
The function only checks divisors up to the square root of the number, avoiding unnecessary checks.
Practice Project: Temperature Conversion
Functions are useful for encapsulating mathematical formulas.
1def celsius_to_fahrenheit(celsius): 2 return (celsius * 9 / 5) + 32 3 4 5def fahrenheit_to_celsius(fahrenheit): 6 return (fahrenheit - 32) * 5 / 9 7 8 9print(celsius_to_fahrenheit(25)) 10print(fahrenheit_to_celsius(77))
Output:
177.0 225.0
Practice Project: Simple Calculator With Functions
Instead of placing all calculations inside one large block, separate each operation into a function.
1def add(a, b): 2 return a + b 3 4 5def subtract(a, b): 6 return a - b 7 8 9def multiply(a, b): 10 return a * b 11 12 13def divide(a, b): 14 if b == 0: 15 raise ValueError("Cannot divide by zero.") 16 17 return a / b 18 19 20first = float(input("Enter first number: ")) 21second = float(input("Enter second number: ")) 22operator = input("Enter operator (+, -, *, /): ").strip() 23 24try: 25 if operator == "+": 26 result = add(first, second) 27 elif operator == "-": 28 result = subtract(first, second) 29 elif operator == "*": 30 result = multiply(first, second) 31 elif operator == "/": 32 result = divide(first, second) 33 else: 34 raise ValueError("Invalid operator.") 35 36 print("Result:", result) 37 38except ValueError as error: 39 print("Error:", error)
This structure is easier to extend because each operation has its own responsibility.
Function Type Hints
Python allows you to add type hints to function parameters and return values.
1def add(a: int, b: int) -> int: 2 return a + b
Another example:
1def greet(name: str) -> str: 2 return f"Hello, {name}!"
Type hints improve readability and help development tools detect potential type-related problems.
They do not automatically enforce types at runtime.
Docstrings
A docstring documents what a function does.
1def calculate_area(length: float, width: float) -> float: 2 """Return the area of a rectangle.""" 3 return length * width
You can access the documentation with:
1print(calculate_area.__doc__)
Docstrings are especially useful for functions that are part of larger projects or reusable modules.
Function Design Best Practices
Good functions usually have a clear responsibility.
Instead of creating one very large function:
1def process_everything(): 2 # validate input 3 # calculate data 4 # save data 5 # format output 6 # send notification 7 ...
prefer smaller functions when the logic is genuinely independent:
1def validate_input(data): 2 ... 3 4 5def calculate_result(data): 6 ... 7 8 9def save_result(result): 10 ... 11 12 13def send_notification(result): 14 ...
This makes individual pieces easier to test, reuse, and maintain.
Common Mistakes With Functions
Forgetting Parentheses
Incorrect:
1def hello: 2 print("Hello")
Correct:
1def hello(): 2 print("Hello")
Forgetting to Call the Function
Defining a function does not execute it.
1def greet(): 2 print("Hello")
Nothing is printed until you call:
1greet()
Forgetting return
Incorrect:
1def add(a, b): 2 a + b 3 4result = add(5, 3) 5 6print(result)
Output:
1None
Correct:
1def add(a, b): 2 return a + b
Confusing print() With return
This:
1def add(a, b): 2 print(a + b)
prints the result but does not return it.
This:
1def add(a, b): 2 return a + b
returns the value so the caller can use it.
Accessing a Local Variable Outside Its Function
1def demo(): 2 number = 10 3 4demo() 5 6print(number)
This raises:
1NameError
because number belongs to the local scope of demo().
Calling a Function With Missing Arguments
1def greet(name): 2 print(f"Hello {name}") 3 4greet()
This raises:
1TypeError
because the required name argument was not provided.
Mutable Default Arguments
A common Python mistake is using a mutable object such as a list as a default argument.
Avoid:
1def add_item(item, items=[]): 2 items.append(item) 3 return items
The same list can be reused across function calls.
Prefer:
1def add_item(item, items=None): 2 if items is None: 3 items = [] 4 5 items.append(item) 6 return items
Now each call without an explicit list gets a fresh list.
Function Arguments: Quick Summary
| Type | Example |
|---|---|
| Positional | add(10, 20) |
| Keyword | add(a=10, b=20) |
| Default | def greet(name="Guest") |
| Variable positional | def total(*numbers) |
| Variable keyword | def profile(**details) |
Important Function Concepts
| Concept | Description |
|---|---|
def | Defines a function |
| Parameter | Variable declared by a function |
| Argument | Value passed to a function |
return | Sends a value back to the caller |
| Default parameter | Parameter with a predefined value |
| Keyword argument | Argument passed using a parameter name |
*args | Accepts multiple positional arguments |
**kwargs | Accepts multiple keyword arguments |
| Lambda | Small anonymous function |
| Scope | Determines where variables can be accessed |
| Recursion | Function calling itself |
| Docstring | Documentation inside a function |
Practice Exercises
Exercise 1: Square a Number
Create a function named square() that accepts a number and returns its square.
Example:
1Input: 8 2Output: 64
Exercise 2: Check Even or Odd
Create a function:
1def is_even(number): 2 ...
It should return True for even numbers and False for odd numbers.
Exercise 3: Find the Largest Number
Create a function that accepts three numbers and returns the largest.
1def largest(a, b, c): 2 ...
Exercise 4: Count Vowels
Create a function that accepts a string and returns the number of vowels.
Example:
1Input: Programming 2Output: 3
Exercise 5: Reverse a String
Create a function that accepts a string and returns its reverse.
1def reverse(text): 2 return text[::-1]
Exercise 6: Check Prime Number
Create an is_prime() function that returns a Boolean result.
1print(is_prime(17)) 2print(is_prime(20))
Expected output:
1True 2False
Exercise 7: Calculate Average
Create a function that accepts any number of numeric arguments and returns their average.
1def average(*numbers): 2 ...
Handle the case where no numbers are provided.
Exercise 8: Fibonacci Function
Create a function that returns the first n Fibonacci numbers.
Example:
1fibonacci(7)
Expected result:
1[0, 1, 1, 2, 3, 5, 8]
Key Takeaways
- Functions are reusable blocks of code designed for specific tasks.
- Use
defto define a function. - Call a function using its name followed by parentheses.
- Parameters receive values inside a function.
- Arguments are the actual values passed during a function call.
- Positional arguments are matched according to their position.
- Keyword arguments explicitly identify parameters.
- Default parameters provide fallback values.
returnsends a value back to the caller.- A function without an explicit
returnreturnsNone. *argshandles variable numbers of positional arguments.**kwargshandles variable numbers of keyword arguments.- Lambda functions are useful for small expressions and callbacks.
- Local variables normally exist only inside their function.
- Global variables can be accessed from functions, but excessive global state should generally be avoided.
- Nested functions can be used to create closures.
- Recursive functions call themselves and require a base case.
- Type hints improve readability and tooling support.
- Docstrings make reusable functions easier to understand.
- Small, focused functions are generally easier to test and maintain.
Functions are a major building block of Python programming. Once you understand functions, you can combine them with loops, conditional statements, lists, dictionaries, modules, classes, exception handling, and APIs to build larger and more maintainable applications.