Unit 4: Modules and Exception Handling
I. Orientation
Python programs become reliable and maintainable when they separate reusable functionality into modules and handle abnormal conditions explicitly. A module is a Python file containing definitions and statements; an exception is an event representing an error or unusual condition during execution. Together, modular design and exception handling support reuse, clear responsibility, controlled failure, and safe resource management.
- Governing principle: anticipate operations that may fail, isolate risky code, and provide an appropriate response rather than allowing an uncontrolled crash.
- Python convention: exceptions are objects, commonly built-in classes such as
ValueError,TypeError,FileNotFoundError, andZeroDivisionError. - Control-flow rule: a
tryblock contains risky statements; matchingexceptblocks handle failures;elsehandles successful execution;finallyperforms compulsory cleanup. - Modularity rule: definitions belong in importable modules, while executable demonstrations should normally be protected by
if __name__ == "__main__":. - Maintainability principle: prefer small functions, descriptive names, narrow exception handling, documented interfaces, and standard-library solutions.
II. Exception Handling — controlled responses to runtime failure
Exception handling changes an error from an abrupt termination into a controlled branch of program execution. A handler should address a condition the program can meaningfully recover from or report.
A. Handling exceptions using try and except
try and except surround an operation that may raise an exception and specify what should happen when it does.
- Basic structure: statements in
tryrun first; if an exception occurs, Python searches for a matchingexcept.
try:
number = int(input("Enter an integer: "))
result = 10 / number
except ValueError:
print("Input must be an integer.")
except ZeroDivisionError:
print("The integer cannot be zero.")Here, ValueError concerns conversion and ZeroDivisionError concerns division.
- Normal flow: if no exception occurs, all
trystatements finish and theexceptblocks are skipped. - Failure flow: once an exception is raised, the remaining statements in
tryare abandoned and control moves to the first matching handler. - Narrow scope: put only operations that may fail inside
try; otherwise unrelated programming errors may be hidden.
B. Multiple exception handlers
Multiple handlers distinguish different failures and allow precise responses instead of one vague error message.
- Specificity first: place subclasses before broader classes; for example, catch
FileNotFoundErrorbeforeOSError. - Shared response: related exceptions can be grouped in a tuple.
try:
value = int(text)
except (TypeError, ValueError):
value = 0The tuple means either TypeError or ValueError receives the same treatment.
- Exception details:
as errorbinds the exception object, which may contain useful context.
except OSError as error:
print(f"File operation failed: {error}")- Broad catches:
except Exception:catches most ordinary exceptions but should be used only at a deliberate boundary, such as a command-line entry point.
C. Raising exceptions
raise deliberately signals that a condition violates a function’s contract or cannot be processed safely.
- Explicit failure:
raise ValueError("age must be non-negative")creates aValueErrorwith a diagnostic message. - Validation point: raising close to the invalid input makes the source of the problem clear.
def set_age(age):
if age < 0:
raise ValueError("age must be non-negative")
return age- Re-raising: a bare
raiseinside anexceptblock preserves the original exception and traceback after logging or adding local action. - Exception chaining:
raise NewError(...) from errorrecords that one failure caused another, useful when translating low-level errors into domain-level errors.
D. Exceptions with functions
Functions use exceptions to communicate failure to their callers while keeping normal results separate from error handling.
- Contract: a function may return a valid value or raise a documented exception; callers decide whether to recover.
- Propagation: if a function does not catch an exception, it travels up the call stack until a matching handler is found.
def reciprocal(value):
return 1 / value
try:
print(reciprocal(0))
except ZeroDivisionError:
print("No reciprocal exists for zero.")- Caller responsibility: catch an exception only when the caller can take useful action; otherwise allow it to propagate.
- Avoid sentinel confusion: returning
Noneor-1for every failure can confuse valid results with errors; exceptions make abnormal conditions explicit.
E. Using finally to clean up
finally contains code that must run whether the operation succeeds, fails, or exits through a return.
- Cleanup guarantee: closing a file, releasing a lock, or disconnecting a resource belongs in
finally. - Execution order: Python runs
try, possibly anexceptorelse, and thenfinally.
file = None
try:
file = open("data.txt", encoding="utf-8")
contents = file.read()
finally:
if file is not None:
file.close()- Preferred modern form:
with open(...)uses a context manager to perform equivalent cleanup automatically. - Return caution: a
returninfinallycan suppress an exception or replace an earlier return, so it should normally be avoided.
F. Custom exception types
Custom exceptions represent application-specific failures and make handlers communicate domain meaning.
- Definition: subclass
Exception, notBaseException, for ordinary application errors.
class InsufficientFundsError(Exception):
"""Raised when an account lacks the requested balance."""
pass- Use:
raise InsufficientFundsError("balance is too low")distinguishes a banking rule from a genericValueError. - Hierarchy: related errors can share a base class, allowing either specific handling or one family-level handler.
- Design limit: create a custom type when callers need to distinguish the condition; do not create a new class for every message.
III. Modules — reusable units of Python code
A module is a .py file whose variables, functions, classes, and executable statements can be used by another program. Modules reduce duplication and establish clear interfaces.
A. Modules
Modules organize related definitions under a namespace, preventing accidental name collisions.
- Creation: a file named
temperature.pybecomes moduletemperature; a function defined there can be imported elsewhere. - Namespace access:
import temperaturerequirestemperature.celsius_to_fahrenheit(20), making the source explicit. - Import execution: Python executes a module’s top-level statements on its first import and normally caches it in
sys.modules. - Public interface: names intended for users should be documented; helper names often begin with
_, such as_validate_input.
B. Finding modules
Python searches for importable modules using its module search path, available as sys.path.
- Search locations: these commonly include the script directory, installed-package directories, and entries from
PYTHONPATH. - Inspection:
import sys
print(sys.path)Each string is a directory Python examines for a matching file or package.
- Name conflicts: a local file named
datetime.pycan shadow the standard-librarydatetimemodule; use distinctive filenames. - Import errors:
ModuleNotFoundErrorusually means the module is absent from the environment or outside the search path.
C. Importing specific names from a module
from module import name places selected definitions directly in the current namespace.
- Selective import:
from math import sqrt, pipermitssqrt(25)andpiwithout themath.prefix. - Alias:
from datetime import datetime as DateTimegives an imported name a local alias. - Trade-off: direct imports are concise but can hide where a name originated or collide with another name.
- Avoid wildcard imports:
from math import *makes the namespace unclear and is poor practice in maintainable programs.
D. Executing modules as scripts
A module may be both imported as a library and run directly as a command-line script.
- Main guard:
def main():
print("Program started")
if __name__ == "__main__":
main()When run directly, __name__ is "__main__"; when imported, it is the module’s name.
- Purpose: definitions remain reusable without automatically running demonstration or command-line code during import.
- Entry-point design: place orchestration in
main()and keep reusable logic in separate functions.
E. Reloading modules
Reloading reruns a previously imported module, mainly during interactive development.
- Mechanism:
import importlib
import settings
importlib.reload(settings)importlib.reload() re-executes the module object.
- Limitation: existing names imported directly with
from settings import VALUEmay not update automatically. - Production caution: reload can leave old objects, class instances, or state in memory; restarting the program is usually safer.
IV. Packages and standard library — organized code collections
Packages group related modules in a directory hierarchy, while the standard library supplies tested modules distributed with Python.
A. Packages and standard library
Packages support scalable organization; the standard library avoids unnecessary third-party dependencies.
- Package structure:
shop/payment.pymay be imported asshop.payment; modern namespace packages do not always require__init__.py, though it remains useful for package initialization and explicit organization. - Nested imports:
import os.pathaccesses a submodule through its package hierarchy. - Standard library examples:
oshandles operating-system interfaces,jsonhandles JSON data,pathlibhandles paths, andcollectionsprovides specialized containers. - Dependency choice: prefer a standard-library module when it clearly meets the requirement, reducing installation and compatibility concerns.
B. datetime
The datetime module represents dates, times, combined timestamps, and time intervals.
- Core classes:
datestores calendar dates,timestores clock times,datetimestores both, andtimedeltarepresents a duration. - Construction and arithmetic:
from datetime import date, timedelta
today = date.today()
next_week = today + timedelta(days=7)days=7 defines a seven-day duration; adding it produces another date.
- Formatting:
strftime("%Y-%m-%d")formats a date;%Yis the four-digit year,%mthe month, and%dthe day. - Parsing:
datetime.strptime("2025-04-12", "%Y-%m-%d")converts matching text into adatetime. - Time zones: naive objects lack timezone information; aware objects carry timezone context. For real-world timestamps, prefer timezone-aware values and avoid assuming local time is universal.
V. Writing modular, maintainable, error-resilient code — integration principles
Good Python design combines modules, functions, validation, and deliberate exception policies so that change and failure remain localized.
A. Writing modular, maintainable, error-resilient code
Modular code gives each component a focused responsibility and makes failures easier to test, explain, and recover from.
- Single responsibility: separate input, validation, computation, and output; for example,
parse_amount()should not also print user-interface messages. - Explicit interfaces: use parameters, return values, and documented raised exceptions rather than hidden global state.
- Defensive boundaries: validate external data at the boundary, catch expected exceptions narrowly, and preserve tracebacks for unexpected failures.
- Resource safety: use context managers such as
with open(...)so cleanup occurs even when parsing raises an exception. - Testable structure: protect script code with the main guard and keep calculations in importable functions.
- Useful diagnostics: include the operation and relevant context in messages, while avoiding passwords, tokens, or other sensitive data.
- Maintainability balance: do not overuse custom exceptions, reloads, or broad handlers; simple, explicit code is generally the most resilient.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →