1.5 Modular Code & Errors
Modular Code & Error Handling in Python
As Python programs become larger, placing all instructions in a single block makes the code difficult to understand, test, reuse, and maintain.
Python solves this problem through modular programming. Functions, modules, and packages allow a large program to be divided into smaller, manageable components.
Python also provides exception handling so that programs can respond gracefully to errors instead of terminating unexpectedly.
- Why modular programming is important
- Defining and calling functions
- Parameters and arguments
- Return values
- Default and keyword arguments
- Variable-length arguments
- Scope of variables
- Lambda expressions
- Modules and imports
- Creating custom modules
- Packages
- Errors and exceptions
try-exceptelseandfinally- Raising exceptions
- Practical Data Science and automation examples
1. What Is Modular Programming?
Modular programming is the practice of dividing a program into smaller, independent and reusable components called modules.
Each component can perform a specific task.
Example
An automation application could be divided into:
- A module for reading files
- A module for cleaning data
- A module for generating reports
- A module for sending emails
- Code reusability
- Better organization
- Easier testing
- Easier debugging
- Improved maintainability
- Team collaboration
- Reduced code duplication
2. Functions in Python
A function is a reusable block of code designed to perform a particular task.
Functions are defined using the def keyword.
Basic Syntax
def function_name():
statements
Example
def greet():
print("Hello, Python!")
greet()
The function is executed when it is called using
greet().
3. Functions with Parameters
A function can accept information through parameters.
def greet(name):
print(f"Hello, {name}!")
greet("Alex")
greet("Jordan")
Here, name is a parameter and the supplied strings are
arguments.
| Term | Meaning |
|---|---|
| Parameter | Variable defined in the function definition. |
| Argument | Actual value supplied when calling the function. |
4. Returning Values from Functions
A function can send a result back to the caller using the
return statement.
def add(a, b):
return a + b
result = add(10, 20)
print(result)
Output:
30
The return statement terminates the function and
provides its result to the calling code.
5. Returning Multiple Values
Python can return multiple values from a function. They are typically packed into a tuple.
def calculate(a, b):
total = a + b
difference = a - b
return total, difference
total, difference = calculate(20, 8)
print(total)
print(difference)
Output:
28
12
6. Default Arguments
A parameter can have a default value. The default is used when the caller does not provide an argument for that parameter.
def greet(name, message="Welcome"):
print(f"{message}, {name}!")
greet("Alex")
greet("Jordan", "Good morning")
Default arguments make functions more flexible.
7. Keyword Arguments
Arguments can be passed using parameter names.
def student_info(name, age, course):
print(name, age, course)
student_info(
course="Data Science",
name="Alex",
age=20
)
Keyword arguments improve readability and allow arguments to be supplied by name.
8. Positional vs Keyword Arguments
| Type | Example | Characteristic |
|---|---|---|
| Positional |
calculate(10, 20)
|
Position determines the parameter. |
| Keyword |
calculate(a=10, b=20)
|
Parameter name identifies the value. |
9. Variable-Length Arguments
Sometimes a function needs to accept an unknown number of arguments.
*args
*args collects extra positional arguments into a
tuple.
def total(*numbers):
result = 0
for number in numbers:
result += number
return result
print(total(10, 20))
print(total(10, 20, 30, 40))
**kwargs
**kwargs collects extra keyword arguments into a
dictionary.
def display_info(**details):
for key, value in details.items():
print(f"{key}: {value}")
display_info(
name="Alex",
age=20,
course="Python"
)
10. Variable Scope
Scope determines where a variable can be accessed in a program.
Local Variable
def calculate():
value = 100
print(value)
calculate()
The variable value exists within the function's local
scope.
Global Variable
tax_rate = 0.18
def calculate_tax(amount):
return amount * tax_rate
print(calculate_tax(1000))
A global variable can be read from inside a function.
Prefer passing data into functions and returning results rather than relying heavily on global variables.
11. Lambda Expressions
A lambda is a small anonymous function written
using the lambda keyword.
Syntax
lambda arguments: expression
Example
square = lambda x: x ** 2
print(square(5))
Output:
25
Lambda expressions are particularly useful when a short function is required temporarily.
12. Lambda with sorted()
Lambda expressions are frequently used as sorting keys.
students = [
{"name": "Alex", "score": 82},
{"name": "Jordan", "score": 95},
{"name": "Taylor", "score": 76}
]
result = sorted(
students,
key=lambda student: student["score"],
reverse=True
)
print(result)
This sorts the students from highest to lowest score.
13. Lambda with map()
map() applies a function to each item of an iterable.
numbers = [1, 2, 3, 4, 5]
squares = list(
map(lambda x: x ** 2, numbers)
)
print(squares)
Output:
[1, 4, 9, 16, 25]
14. Lambda with filter()
filter() selects elements for which a condition
evaluates to true.
numbers = [10, 15, 20, 25, 30]
even_numbers = list(
filter(lambda x: x % 2 == 0, numbers)
)
print(even_numbers)
Output:
[10, 20, 30]
For many straightforward transformations and filters,
comprehensions are often easier to read than nested
map() and filter() expressions.
15. What Is a Module?
A module is a Python file containing reusable code such as functions, classes, and variables.
A module normally has a .py extension.
Modules allow related functionality to be organized separately.
Example
math_tools.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
16. Importing a Module
Use import to load a module.
import math
print(math.sqrt(25))
print(math.pi)
The module name is used to access its members.
17. from ... import
Specific members can be imported directly.
from math import sqrt
print(sqrt(36))
Multiple members can also be imported.
from math import sqrt, pi
print(sqrt(49))
print(pi)
18. Importing with an Alias
The as keyword creates a shorter or alternative name
for a module.
import math as m
print(m.sqrt(64))
Aliases are widely used in Data Science.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
The aliases np, pd, and
plt are widely recognized conventions for
NumPy, pandas, and Matplotlib.
19. Creating Your Own Module
Suppose a file named calculator.py contains:
def add(a, b):
return a + b
def subtract(a, b):
return a - b
Another Python file can import it:
import calculator
print(calculator.add(10, 5))
print(calculator.subtract(10, 5))
This allows reusable functionality to be maintained separately from the main program.
20. if __name__ == "__main__"
Python modules can contain code that should run only when the file is executed directly, not when it is imported.
def greet():
print("Hello")
if __name__ == "__main__":
greet()
This pattern is commonly used in reusable Python modules.
21. What Is a Package?
A package is a way of organizing related Python modules into a directory structure.
A project might be organized as:
analytics/
data.py
cleaning.py
reports.py
Packages help organize larger applications and libraries.
22. Importing from a Package
from analytics import cleaning
cleaning.remove_duplicates()
Packages make it easier to structure large projects into logical components.
23. Python Standard Library
Python includes a large collection of modules in its standard library.
| Module | Common Purpose |
|---|---|
math |
Mathematical functions. |
random |
Random number generation. |
datetime |
Date and time operations. |
os |
Operating-system interaction. |
pathlib |
Object-oriented filesystem paths. |
json |
JSON encoding and decoding. |
re |
Regular expressions. |
statistics |
Basic statistical calculations. |
24. Errors and Exceptions
Programs can encounter problems during execution. Python reports many runtime problems using exceptions.
Example
number = 10
result = number / 0
This raises a ZeroDivisionError.
Exception handling allows a program to detect and respond to such conditions gracefully.
25. Common Python Exceptions
| Exception | Typical Cause |
|---|---|
ValueError |
Correct type but inappropriate value. |
TypeError |
Operation applied to an inappropriate type. |
ZeroDivisionError |
Division by zero. |
IndexError |
Sequence index is out of range. |
KeyError |
Dictionary key does not exist. |
FileNotFoundError |
Requested file does not exist. |
NameError |
Name or variable is not defined. |
AttributeError |
Object does not have the requested attribute. |
26. try-except
The try block contains code that may raise an
exception. The except block handles the exception.
Syntax
try:
risky_code
except ExceptionType:
handling_code
Example
try:
number = int(input("Enter a number: "))
print(100 / number)
except ValueError:
print("Please enter a valid integer.")
except ZeroDivisionError:
print("Zero cannot be used as the divisor.")
Different exception types can have different handling logic.
27. Catching an Exception Object
The exception object can be stored using as.
try:
number = int("abc")
except ValueError as error:
print("Error:", error)
This can be useful for logging or displaying diagnostic information.
Prefer catching specific exceptions instead of using a broad
except: whenever practical.
28. try-except-else
The else block runs only when no exception occurs in
the try block.
try:
number = int(input("Enter a number: "))
except ValueError:
print("Invalid input.")
else:
print("Valid number:", number)
This keeps successful execution separate from error handling.
29. The finally Block
The finally block runs whether an exception occurs or
not.
try:
number = int(input("Enter a number: "))
except ValueError:
print("Invalid input.")
finally:
print("Program execution completed.")
finally is commonly used for cleanup operations.
30. Complete try-except-else-finally Structure
try:
number = int(input("Enter a number: "))
result = 100 / number
except ValueError:
print("Invalid number.")
except ZeroDivisionError:
print("Cannot divide by zero.")
else:
print("Result:", result)
finally:
print("Operation completed.")
| Block | Purpose |
|---|---|
try |
Contains code that may raise an exception. |
except |
Handles specified exceptions. |
else |
Executes when no exception occurs. |
finally |
Executes regardless of whether an exception occurs. |
31. Raising Exceptions
Python allows a program to explicitly raise an exception using
the raise statement.
age = -5
if age < 0:
raise ValueError("Age cannot be negative")
Raising exceptions is useful for enforcing rules and validating program inputs.
32. Input Validation with Exceptions
def calculate_percentage(marks, total):
if total <= 0:
raise ValueError("Total marks must be positive.")
return (marks / total) * 100
try:
percentage = calculate_percentage(450, 500)
print(f"Percentage: {percentage:.2f}%")
except ValueError as error:
print("Error:", error)
This pattern is useful when building reliable data-processing functions.
33. Custom Exceptions
Python also allows developers to define their own exception classes for application-specific errors.
class InvalidScoreError(Exception):
pass
score = 120
if score > 100:
raise InvalidScoreError("Score cannot exceed 100.")
Custom exceptions are particularly useful in larger applications and reusable libraries.
34. Exception Handling with Files
File operations can fail when a file does not exist or cannot be accessed.
try:
with open("data.csv", "r") as file:
content = file.read()
except FileNotFoundError:
print("The requested file was not found.")
else:
print("File loaded successfully.")
finally:
print("File operation completed.")
This pattern is useful in automation scripts that process files supplied by users or external systems.
35. Data Science Example — Statistical Function
Functions make repeated calculations reusable.
def average(values):
if not values:
raise ValueError("The collection cannot be empty.")
return sum(values) / len(values)
scores = [82, 91, 76, 88, 95]
try:
result = average(scores)
print(f"Average: {result:.2f}")
except ValueError as error:
print("Error:", error)
The function validates its input and returns a calculated result.
36. Automation Example — Safe Data Processing
Modular functions and exception handling can be combined to create safer automation workflows.
def convert_amount(value):
try:
return float(value)
except ValueError:
return None
data = ["1200", "850.50", "invalid", "2400"]
valid_amounts = []
for value in data:
amount = convert_amount(value)
if amount is not None:
valid_amounts.append(amount)
print(valid_amounts)
Invalid input is handled without stopping the entire processing operation.
37. Mini Project — Data Utility Module
Suppose a file named data_utils.py contains:
def clean_numbers(values):
return [
float(value)
for value in values
if value is not None
]
def average(values):
if not values:
raise ValueError("No values available.")
return sum(values) / len(values)
Another program can reuse these functions:
import data_utils
data = [10, 20, 30, 40]
cleaned = data_utils.clean_numbers(data)
print(data_utils.average(cleaned))
Separate reusable logic from the main application whenever doing so improves clarity and maintainability.
38. Good Function Design
Well-designed functions generally have a clear responsibility.
Less Reusable
def process_data():
# Read data
# Clean data
# Calculate statistics
# Generate report
# Send email
More Modular
def read_data():
...
def clean_data():
...
def calculate_statistics():
...
def generate_report():
...
def send_email():
...
Smaller functions can be tested and reused independently.
39. Function Documentation with Docstrings
A docstring documents what a function, class, or module does.
def calculate_average(values):
"""Return the arithmetic mean of a collection of numbers."""
if not values:
raise ValueError("Values cannot be empty.")
return sum(values) / len(values)
Docstrings make reusable code easier to understand and maintain.
40. Common Mistakes
| Mistake | Problem | Better Approach |
|---|---|---|
| Repeating the same code | Increases maintenance effort. | Extract reusable logic into functions. |
| Huge functions | Difficult to test and understand. | Divide responsibilities into smaller functions. |
| Using many global variables | Creates hidden dependencies. | Prefer parameters and return values. |
Bare except: |
Can hide unexpected errors. | Catch specific exceptions where possible. |
| Ignoring exception details | Makes debugging difficult. | Log or inspect the exception appropriately. |
| Putting everything in one module | Large projects become difficult to maintain. | Organize related functionality into modules/packages. |
41. Python Interview Questions
Q1. What is a function in Python?
View Answer
A function is a reusable block of code designed to perform a particular task. It can accept parameters and return values.
Q2. What is the difference between a parameter and an argument?
View Answer
A parameter is a variable defined in a function definition, while an argument is the actual value passed to the function during a call.
Q3. What is a lambda function?
View Answer
A lambda is a small anonymous function defined using the
lambda keyword, generally containing a single
expression.
Q4. What is a module?
View Answer
A module is a Python file containing reusable code such as functions, classes, and variables.
Q5. What is the purpose of exception handling?
View Answer
Exception handling allows a program to detect and respond to runtime problems without necessarily terminating unexpectedly.
Q6. What is the difference between else and finally in exception handling?
View Answer
else executes when no exception occurs in the
try block, while finally executes
regardless of whether an exception occurred.
Q7. What does the raise statement do?
View Answer
raise explicitly triggers an exception,
allowing a program to enforce validation rules or report
application-specific errors.
Q8. What are *args and **kwargs?
View Answer
*args collects additional positional arguments
into a tuple, while **kwargs collects
additional keyword arguments into a dictionary.
42. Examination Questions
Multiple Choice Questions
Q1. Which keyword is used to define a function in Python?
functiondefdefinefunc
Answer: B — def
Q2. Which statement sends a value back from a function?
sendoutputreturnyield-value
Answer: C — return
Q3. Which keyword is used to handle an exception?
catchexcepterrorhandle
Answer: B — except
Q4. Which block executes regardless of whether an exception occurs?
tryexceptelsefinally
Answer: D — finally
Q5. What is the purpose of a module?
- To delete variables
- To organize and reuse Python code
- To replace Python syntax
- To prevent functions from executing
Answer: B — To organize and reuse Python code
Short Answer Questions
- Define a function and state two advantages of using functions.
- Differentiate between positional and keyword arguments.
- What is a lambda expression? Give an example.
- Explain the difference between a module and a package.
-
Explain the purpose of
try,except,else, andfinally. - What is exception handling and why is it useful?
-
Explain
*argsand**kwargs. -
What is the purpose of the
raisestatement?
43. Practical Challenge
Build a Modular Data Processing Utility
Create a small Python project that demonstrates functions, modules, and exception handling.
-
Create a module named
data_utils.py. - Add a function that validates numeric input.
- Add a function that calculates an average.
- Add a function that identifies the maximum value.
-
Raise a
ValueErrorwhen an empty collection is supplied. - Import the module into a separate main program.
-
Use
try-exceptto handle invalid input. - Display a clear final report.
44. Concept Map
| Concept | Key Idea |
|---|---|
| Function | Reusable block of code. |
| Parameter | Input variable in a function definition. |
| Argument | Value passed to a function. |
| return | Sends a result back to the caller. |
| Lambda | Small anonymous function. |
| Module | Python file containing reusable code. |
| Package | Organized collection of related modules. |
| try | Code that may raise an exception. |
| except | Handles an exception. |
| else | Runs when no exception occurs. |
| finally | Runs regardless of exception outcome. |
| raise | Explicitly triggers an exception. |
45. Quick Reference
| Task | Python Syntax |
|---|---|
| Define function |
def function():
|
| Return value |
return value
|
| Lambda |
lambda x: x * 2
|
| Import module |
import module
|
| Import specific member |
from module import function
|
| Handle exception |
try ... except
|
| Run after success |
else
|
| Always execute cleanup |
finally
|
| Raise exception |
raise ValueError(...)
|
| Variable positional arguments |
*args
|
| Variable keyword arguments |
**kwargs
|
46. Key Takeaways
- Functions make Python programs reusable and modular.
- Parameters define the inputs a function accepts.
- Arguments are the actual values supplied to a function.
-
returnsends a result back to the caller. - Default and keyword arguments improve function flexibility.
-
*argshandles variable positional arguments. -
**kwargshandles variable keyword arguments. - Lambda expressions are useful for short, temporary functions.
- Modules organize reusable Python code.
- Packages organize related modules into larger project structures.
- Exception handling prevents expected runtime problems from unnecessarily terminating a program.
- Catch specific exceptions whenever practical.
-
elseexecutes after successfultryexecution. -
finallyis used for code that should execute regardless of the exception outcome. -
raiseallows programs to explicitly report invalid conditions. - Modular code and robust error handling are essential for production-quality Data Science and automation applications.
Divide large programs into reusable functions and modules, and handle expected failures explicitly so your Python applications remain readable, maintainable, and reliable.