7.2 Modules and packages

In Python, a module is a file containing Python code. A module can define functions, classes, and variables.

A package is a collection of modules. Packages are used to organize related modules and provide a way to manage namespaces.

A complete program can separate calculations from the actions that read data, display results, or write files. A calculation module gives each function a clear responsibility and explicit inputs and outputs.

A main script imports those functions and controls the order of the program's actions.

The time-calculation example below uses util.py for reusable calculations and main.py to run a small travel-time report.

Later chapters use a project environment to install testing tools and Git to preserve a working version. Testing a calculation independently helps you locate a defect before it affects the complete report.

In the following sections, you'll learn how to create and use modules and packages in Python.

7.2.1 Modules

A module is a file containing Python definitions and statements. The file name is the module name with the .py extension appended. You can import a module using the import statement.

For example, Listing 1 presents a module util.py, which contains a series of time calculation functions. To use these functions in another Python script, you can import the util module using the import statement.

Listing 1: File util.py (module util).
# util.py

# Constants
AVG_SPEED_KMPH = 50

def calculate_time_h(
        distance_km,
        speed_kmph=AVG_SPEED_KMPH):
    """Calculate time in hours"""
    time_hours = distance_km / speed_kmph
    return time_hours

def to_minutes(time_hours):
    """Convert time in hours to minutes"""
    return time_hours * 60

def is_time_between(start_time, end_time, check_time):
    """Check if the check_time is between start_time and end_time"""
    return start_time <= check_time <= end_time

Importing a module into a script

To call a function after import util, prefix its name with the module name, such as util.calculate_time_h. Your IDE may provide code completion when you type the module name and a dot, such as util..

Keep util.py and main.py in the same directory. Listing 2 imports util, calculates travel time, converts it to minutes, and prints the travel time and average speed.

Listing 2: File main.py. Import util to calculate travel time and report the average speed.
# main.py
import util

# Calculate time to travel 100 km at 50 km/h
time_hours = util.calculate_time_h(100, 50)
print(f"Travel Time: {time_hours:.2f} hours")

# Convert time to minutes
time_minutes = util.to_minutes(time_hours)

# Average speed in km/h
print(f"Average speed: {util.AVG_SPEED_KMPH} km/h")

Importing specific functions from a module

You can import specific functions from a module using the from statement. This way, you can use the function names directly without prefixing them with the module name.

For example, Listing 3 shows how to import the is_time_between function from the util module. In this case, you can use the function directly without prefixing it with the module name.

Listing 3: Example of importing the is_time_between function from the util module in a Python script main.py.
# main.py
from util import is_time_between

# Check if 12:00 is between 10:00 and 14:00
is_between = is_time_between(10, 14, 12)
print(f"Is 12:00 between 10:00 and 14:00? {is_between}")

You can also import multiple functions from a module by separating the function names with commas in the from statement. If you need to add new lines to the from statement, use parentheses to group the function names.

Listing 4 imports the calculate_time_h and to_minutes functions and the constant AVG_SPEED_KMPH from util.

Listing 4: Example of importing multiple functions from the util module in a Python script main.py.
# main.py
from util import (
    calculate_time_h,
    to_minutes,
    AVG_SPEED_KMPH
)

# Calculate time to travel 100 km at 50 km/h
time_hours = calculate_time_h(100, 50)

# Convert time to minutes
time_minutes = to_minutes(time_hours)

# Average speed in km/h
print(f"Average speed: {AVG_SPEED_KMPH} km/h")

Importing all functions from a module

You can import the public names from a module using the * wildcard in the from statement. These names can include constants and functions.

A module can control them with __all__; otherwise, names beginning with _ are omitted. You can use the imported function names directly without prefixing them with the module name.

For example, Listing 5 shows how to import all functions from the util module. In this case, you can use the function names directly without prefixing them with the module name.

Listing 5: Example of importing all functions from the util module in a Python script main.py.
# main.py
from util import *

# Calculate time to travel 100 km at 50 km/h
time_hours = calculate_time_h(100, 50)

# Convert time to minutes
time_minutes = to_minutes(time_hours)

# Check if 12:00 is between 10:00 and 14:00
is_between = is_time_between(10, 14, 12)
CautionBest practice when importing functions

Import specific functions or use the module name as a prefix when calling functions. A wildcard import can save typing, but it can lead to naming conflicts and make the source of a name harder to identify.

Aliasing module names

You can alias a module name when importing it using the as keyword. This way, you can use the alias instead of the full module name in your code.

For example, Listing 6 shows how to import the util module with the alias u. In this case, you can use the alias u instead of the full module name util when calling the functions.

Listing 6: Example of importing the util module with the alias u in a Python script main.py.
# main.py
import util as u

# Calculate time to travel 100 km at 50 km/h
time_hours = u.calculate_time_h(100, 50)

Importing in the interactive interpreter

You can also import modules in the Python interactive interpreter. When you import a module, you can use the functions and variables defined in the module directly in the interpreter.

Start the interpreter in the directory containing util.py, or add that directory to the module search path. In Listing 7, that directory is C:\dev\project.

The ls command lists the files so you can check that util.py is present before importing it. The example then imports util and calls its time-calculation function.

Listing 7: Importing util and calculating travel time in the Python interactive interpreter.
C:\dev\project> ls
util.py
C:\dev\project> python
Python 3.13.0 on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import util
>>> util.calculate_time_h(100, 50)
2.0
>>> exit()
C:\dev\project>

Using modules as scripts

When you run a Python module as a script, the code in the module is executed. You can use the __name__ variable to check whether the module is being run as a script or imported into another module.

When the module runs as a script, __name__ is "__main__". Listing 8 defines main and places its call under this check, so main runs only when util.py runs as a script.

Listing 8: Define main in util.py and call it only when the module runs as a script.
# util.py

def calculate_time_h(
        distance_km,
        speed_kmph=50):
    """Calculate time in hours"""
    time_hours = distance_km / speed_kmph
    return time_hours

def to_minutes(time_hours):
    """Convert time in hours to minutes"""
    return time_hours * 60

def main():
    """Main function"""
    print("Running util.py as a script")
    time_hours = calculate_time_h(100, 50)
    print(f"Travel Time: {time_hours:.2f} hours")

if __name__ == "__main__":
    main()

To run the module as a script, use the command in Listing 9.

Listing 9: Running util.py as a script. The main function runs and prints two lines.
C:\dev\project> python util.py
Running util.py as a script
Travel Time: 2.00 hours

When you run the module as a script, the main function is executed, and the output is displayed in the console. When another script imports util, __name__ is "util" instead, so main does not run.

NoteUsing if __name__ == '__main__'

The if __name__ == '__main__' guard controls the statements indented under it.

The functions defined above the guard remain available after import. The guard prevents the call to main() from running during import.

7.2.2 Packages

A package is a collection of modules that are organized in a directory structure. Packages are used to group related modules and provide a way to manage namespaces.

In the regular packages used here, the package directory contains an __init__.py file and one or more modules. The __init__.py file can be empty or contain initialization code for the package.

For example, a package named vrp can contain modules for vehicle routing problems (VRP), including models and utility functions. Listing 10 shows the package inside a project folder.

The model subpackage contains milp.py and heuristics.py. The util subpackage contains time.py, distance.py, and network.py; ... indicates other modules. The main.py script is the application's main entry point.

Listing 10: Project structure for the vrp package, its model and util subpackages, and the main script.
project/                   Project folder
│
├── vrp/                   Top-level package
│   ├── __init__.py        Package initialization file
│   ├── model/             Subpackage for VRP models
│   │   ├── __init__.py    Subpackage initialization file
│   │   ├── milp.py        Module for mixed-integer linear programming models
│   │   ├── heuristics.py  Module for heuristic algorithms
│   │   └── ...
│   └── util/              Subpackage for utility functions
│       ├── __init__.py    Subpackage initialization file
│       ├── time.py        Module for time-related functions
│       ├── distance.py    Module for distance-related functions
│       ├── network.py     Module for network-related functions
│       └── ...
└── main.py                Main script
TipWhy __init__.py?

The __init__.py files identify the regular packages used in this example. Python also supports namespace packages without this file; that package form is outside this example.

Importing modules from a package

You can import modules from a package using the import statement. When importing a module from a package, you need to specify the package name and the module name separated by a dot.

Listing 11 imports milp from the model subpackage of vrp. The full dotted path names every level, and the alias milp keeps the calls short.

The call to create_vrp_model is schematic. That function must already exist in milp.py; no model implementation is supplied here. The example demonstrates the import path and alias, so it is not a complete routing program.

Listing 11: File main.py. Import vrp.model.milp with the alias milp.
# main.py
import vrp.model.milp as milp

# Create a VRP model
n_customers = 10
n_vehicles = 3
m = milp.create_vrp_model(
    n_customers,
    n_vehicles)

If you want to import a module from a package and use it without prefixing it with the package name, you can use the from statement. In Listing 12, we import a function from time.py in the util subpackage and use it directly without prefixing it with the package name.

Listing 12: File main.py. Example of importing a function from the time.py module in the util subpackage of the vrp package.
# main.py
from vrp.util.time import calculate_time_h

# Calculate time in hours
time_hours = calculate_time_h(100, 50)

7.2.3 Importing modules and packages in IPython

In IPython, you can import modules and packages using the import statement. You can also use the from statement to import specific functions from a module or package.

The directory containing the module, or the parent directory of the package, must be on the module search path. The current working directory is normally on that path in an interactive session. You can add a directory using sys.path.append().

For example, Listing 13 shows how to import the util module in IPython and use the functions defined in the module. In this case, the util.py file is in the current directory, so you can import the module directly.

Listing 13: Example of importing the util module in IPython and using the functions defined in the module. The util.py file is in the current directory.
In [1]: import util
In [2]: util.calculate_time_h(100, 50)
Out[2]: 2.0

If the module is not in the current directory, you can add its directory to the Python path with sys.path.append(). Listing 14 shows a vrp package alongside a notebooks folder for Jupyter notebooks.

The notebook vrp_analysis.ipynb is inside notebooks. It needs to import the time module from the util subpackage of vrp.

Listing 14: Project structure with the vrp package and the vrp_analysis.ipynb notebook in separate folders.
project/                   Project folder
│
├── vrp/                   Top-level package
│   ├── __init__.py        Package initialization file
│   ├── model/             Subpackage for VRP models
│   │   ├── __init__.py    Subpackage initialization file
│   │   ├── milp.py        Module for mixed-integer linear programming models
│   │   ├── heuristics.py  Module for heuristic algorithms
│   │   └── ...
│   └── util/              Subpackage for utility functions
│       ├── __init__.py    Subpackage initialization file
│       ├── time.py        Module for time-related functions
│       ├── distance.py    Module for distance-related functions
│       ├── network.py     Module for network-related functions
│       └── ...
├── main.py                Main script
└── notebooks/             Folder for Jupyter notebooks
    └── vrp_analysis.ipynb Notebook for VRP analysis

The sys.path variable is a list of directories where Python looks for modules. To import vrp.util.time from the notebook, add the project folder containing vrp to that list.

Run Listing 15 with the notebook's current working directory set to notebooks. The relative path ../ moves up one directory level to the project folder. A relative path is interpreted from the current working directory.

If the notebook starts in the project folder, the package is already reachable and the path adjustment is unnecessary.

Listing 15: Add the project folder to the module search path and import vrp.util.time in the notebook.
import sys
sys.path.append('../')
import vrp.util.time as vrp_time
vrp_time.calculate_time_h(100, 50)

After adding the project folder, the notebook can import vrp.util.time and call its time-calculation function. You can also add an absolute path, which names the directory directly, with a call such as sys.path.append('C:/dev/project').

7.2.4 The standard library

A library provides functions and other components that can be reused instead of implemented from the beginning. The term library describes a collection of modules and packages that provide specific functionality.

Python includes a standard library with modules for mathematics, text, dates, files, statistics, randomness, and many other common tasks.

Using a library still requires judgment. You need to understand what a function receives, what it returns, and which assumptions its result depends on.

Listing 16 imports the math module from the standard library and calls one of its functions.

Listing 16: Importing the math module and calling math.sqrt.
import math

travel_distance = math.sqrt(3 ** 2 + 4 ** 2)
print(travel_distance)

The prefix math. makes the source of sqrt explicit. For this course, prefer explicit module names over wildcard imports.

TipWhere are standard library modules?

Standard library modules are included with the Python installation. You can find them in the Lib directory of your Python installation. For example, an installation in C:\Python has its library modules in C:\Python\Lib.

In VS Code, you can usually navigate to the module definition by holding the Ctrl key (or Cmd on macOS) and clicking on the module name in the import statement.

Use the statistics module

The standard library also provides common descriptive statistics. Listing 17 summarizes a list of waiting times with the statistics module.

Listing 17: Calculating the mean and the median of a list with the statistics module.
import statistics

waiting_times = [4, 7, 2, 9, 7, 5, 10, 3]

print(statistics.mean(waiting_times))
print(statistics.median(waiting_times))

Before using a summary, decide what it means and whether the data support it. A correctly calculated average may still be a poor description of a highly uneven distribution.

The random module, used in the Randomness chapter, is another standard-library module.

7.2.5 Useful packages

Python has a vast ecosystem of packages that you can use to extend the functionality of your applications. Here are some popular Python packages that you might find interesting:

  • Mathematics and Statistics:
    • SciPy: A library for scientific computing that provides modules for optimization, integration, interpolation, and linear algebra (SciPy).
    • NumPy: A powerful library for numerical computing that provides support for arrays, matrices, and mathematical functions (NumPy).
  • Data Manipulation and Analysis:
    • Pandas: A data manipulation library that provides data structures like DataFrames and Series for working with structured data (Pandas).
    • pickle: A standard-library module for serializing and deserializing Python objects to and from a byte stream (pickle).
    • json: A standard-library module for encoding and decoding JSON data (json).
    • csv: A standard-library module for reading and writing CSV files (csv).
  • Visualization:
    • Matplotlib: A plotting library that allows you to create a wide variety of plots and charts (Matplotlib).
    • Seaborn: A data visualization library based on Matplotlib that provides a high-level interface for creating attractive and informative statistical graphics (Seaborn).
    • Plotly: An interactive plotting library that allows you to create interactive plots and dashboards (Plotly).
    • Bokeh: A library for creating interactive visualizations and dashboards in web browsers (Bokeh).
  • Map and Geospatial Data:
    • Folium: A library for creating interactive maps and visualizing geospatial data (Folium).
    • Geopandas: A library for working with geospatial data that extends the capabilities of Pandas (Geopandas).
    • Shapely: A library for geometric operations like creating, analyzing, and manipulating planar geometric objects (Shapely).
  • Graphs and Networks:
    • NetworkX: A library for creating, analyzing, and visualizing complex networks and graphs (NetworkX).
    • OSMnx: A library for working with OpenStreetMap data and street networks (OSMnx).
  • Machine Learning and Data Mining:
    • Scikit-learn: A machine learning library that provides tools for data mining and data analysis (Scikit-learn).
    • TensorFlow: An open-source machine learning library developed by Google for building and training machine learning models (TensorFlow).
    • PyTorch: An open-source machine learning library developed by Facebook for building and training machine learning models (PyTorch).
  • Web Development:
    • Django: A web framework for building web applications using the Model-View-Template (MVT) architecture (Django).
    • Flask: A lightweight web framework for building web applications and APIs (Flask).
  • Testing and Quality Assurance:
    • pytest: A testing framework for writing and running tests in Python (pytest).
    • unittest: A testing framework that is included in the Python standard library for writing unit tests (unittest).
  • Mixed Integer Linear Programming:
    • PuLP: A linear programming library that provides a high-level API for defining and solving optimization problems (PuLP).
    • Gurobi: A commercial optimization solver that provides a Python API for solving mixed-integer linear programming problems (Gurobi).
    • CPLEX: A commercial optimization solver that provides a Python API for solving mixed-integer linear programming problems (CPLEX).
    • SCIP: An optimization solver for mixed-integer programming problems, with a Python API (SCIP).
    • Google OR-Tools: A library for optimization problems that provides a Python API for solving mixed-integer linear programming problems (Google OR-Tools).
    • COIN-OR: A collection of open-source optimization solvers that provide Python APIs for solving mixed-integer linear programming problems (COIN-OR).
    • GLPK: An open-source linear programming library that provides a Python API for solving optimization problems (GLPK).
    • CBC: An open-source mixed-integer programming solver from COIN-OR, used by PuLP by default (CBC).
  • Web Scraping and Automation:
    • Requests: A library for making HTTP requests in Python (Requests).
    • Beautiful Soup: A library for parsing HTML and XML documents (Beautiful Soup).
  • Game Development:
    • Pygame: A set of Python modules designed for writing video games (Pygame).
    • Arcade: A modern Python framework for creating 2D video games (Arcade).
  • Computer Vision and Image Processing:
    • Pillow: A library for image processing tasks like opening, manipulating, and saving many different image file formats (Pillow).
    • OpenCV: A library for computer vision and image processing tasks (OpenCV).
  • Natural Language Processing:
    • NLTK: A library for natural language processing tasks like tokenization, stemming, and part-of-speech tagging (NLTK).
    • spaCy: An open-source library for natural language processing tasks like named entity recognition, part-of-speech tagging, and dependency parsing (spaCy).
    • TextBlob: A library for processing textual data that provides tools for sentiment analysis, part-of-speech tagging, and noun phrase extraction (TextBlob).

These are just a few examples of the many Python packages available for different domains and use cases. You can explore the Python Package Index (PyPI) to discover more packages and libraries that can help you in your projects.