13 - Modules and Packages
As programs grow, one file becomes difficult to understand. Modules let you split related code into reusable files.
A simple picture
A module is one labelled drawer of reusable tools. A package is a toolbox containing several related drawers.
expense_app package
├── storage.py -> file tools
├── calculations.py -> maths rules
└── main.py -> starts the program
Importing a standard module
An import asks Python to make code from another module available. The standard library is the collection of modules installed with Python.
import math
print(math.sqrt(25))
The module name keeps the function’s origin visible. You can import a selected name:
from math import pi
print(pi)
Avoid importing everything with from module import *; it makes names unclear.
Important fact: Python executes a module’s top-level statements the first time it is imported in a process. Keep input prompts, file changes, and demonstrations behind functions or the main guard.
Creating a custom module
Create tax.py:
def calculate_tax(amount, rate):
return amount * rate
Create main.py in the same folder:
from tax import calculate_tax
tax = calculate_tax(1000, 0.18)
print(tax)
Python searches the project folder for tax.py when running main.py from that folder.
The main guard
If a module contains demonstration code, protect it:
def calculate_tax(amount, rate):
return amount * rate
if __name__ == "__main__":
print(calculate_tax(1000, 0.18))
When the file is run directly, __name__ is "__main__". When imported, the demonstration does not run.
Aliases
import number_utilities as numbers
print(numbers.is_prime(7))
Aliases are useful for long names, but use clear names and do not hide important meaning.
Packages
A package groups related modules:
expense_app/
__init__.py
storage.py
calculations.py
main.py
storage.py should handle persistence, calculations.py should contain business rules, and main.py should coordinate input and display. This separation makes each file easier to test.
pip and project environments
Third-party packages are installed into the active virtual environment. Always activate .venv before installing project dependencies and record dependencies in a requirements file when the project needs them.
Common mistakes
- Naming a file
math.py,json.py, or another standard-library name. - Running a script from a directory where its module cannot be found.
- Putting input code at module import time.
- Creating circular imports between modules.
- Placing all logic in
main.pyinstead of separating responsibilities.
Bug Hunter
Bug 1 — file hides a standard module
project/
math.py
main.py
Inside main.py, import math may find the project’s math.py instead of the standard library module.
Bug 2 — demonstration runs during import
# tools.py
def double(number):
return number * 2
print(double(10))
Bug 3 — circular imports
students.py imports reports.py
reports.py imports students.py
Show Bug Hunter fixes
- Rename the project file to a specific name such as
math_practice.py. - Put the demonstration under
if __name__ == "__main__":. - Move shared values or functions into a third neutral module, or redesign the responsibilities so the modules do not depend on each other in a circle.
Optional deeper look: what happens during import?
Python searches for a module, creates a module object, executes its top-level code, and stores the result in sys.modules, an internal cache. Later imports in the same process normally reuse that module object instead of executing it again. A partially initialised cached module is one reason circular imports are confusing.
Practice
Try these problems on this page. Draw the files first and write the responsibility of each file.
Problems
- Create a module containing a greeting function.
- Import a function using two different import styles.
- Create a constants module for tax rates.
- Add a
__main__block that demonstrates a module. - Split a calculator into two files.
- Build a module containing number utility functions.
- Import a module with an alias and explain why it helps.
- Organize three related modules into a package.
- Identify and fix a circular-import design.
- Refactor the expense tracker into a small package with a main entry point.
Show hints
- Put the function in
greetings.py. - Try
import moduleandfrom module import name. - Keep named constants in one module.
- Put demonstration code under the main guard.
- Keep calculations separate from input and display.
- Group related number operations in one file.
- An alias can make a long module name easier to use.
- Give each module one clear responsibility.
- Move shared code into a third module.
- Separate storage, calculations, and the program entry point.
Show solution ideas
- Define
greet(name)ingreetings.pyand import it. - Both styles work; choose the one that keeps names clear.
- Put names such as
GST_RATEinrates.py. - Use
if __name__ == "__main__":. - Use
operations.pyandmain.py. - Put prime, factorial, and divisor functions in
number_utils.py. import number_utils as numbers.- Use
storage.py,calculations.py, andmain.py. - Move shared functionality into a neutral module.
- Use separate modules for storage, expense logic, and the entry point.
Homework
Refactor a previous project into at least three modules and document what responsibility belongs in each file.
Checkpoint
Split a calculator into a module containing calculation functions and a separate main program containing input and display logic. Explain what the main guard does.