Python Modules and Packages
Learn how to import modules, create packages, manage pip dependencies, and structure Python projects with virtual environments.
What Is a Module?
A module is any .py file. When you write import math, Python finds math.py (or a compiled equivalent) on sys.path and loads it.
# math is a standard library module
import math
math.sqrt(16) # 4.0
math.pi # 3.141592653589793
math.floor(3.7) # 3
Import Styles
# Import the module — access via dotted name
import os
os.path.join("/home", "alice")
# Import specific names
from os.path import join, exists
join("/home", "alice")
# Import with alias — common for long names
import numpy as np
import pandas as pd
from datetime import datetime as dt
# Import all public names (avoid in production)
from math import *
Conditional Imports
try:
import ujson as json # fast C library
except ImportError:
import json # fallback to stdlib
The name Guard
# utils.py
def add(a, b):
return a + b
if __name__ == "__main__":
# Only runs when this file is executed directly
# Not when imported as a module
print(add(2, 3))
This pattern lets a file work as both a reusable module and a runnable script.
Creating a Package
A package is a directory with an __init__.py:
myapp/
├── __init__.py
├── auth/
│ ├── __init__.py
│ ├── tokens.py
│ └── passwords.py
├── api/
│ ├── __init__.py
│ ├── routes.py
│ └── middleware.py
└── utils.py
__init__.py can be empty or can define what gets exported:
# myapp/auth/__init__.py
from .tokens import generate_token, verify_token
from .passwords import hash_password, check_password
__all__ = ["generate_token", "verify_token", "hash_password", "check_password"]
Now users can do:
from myapp.auth import generate_token
# instead of:
from myapp.auth.tokens import generate_token
Relative Imports
Within a package, use relative imports to reference sibling modules:
# myapp/api/routes.py
from ..auth import verify_token # go up one level to myapp, then into auth
from ..utils import format_response # sibling in myapp
from .middleware import rate_limit # sibling in myapp/api
Relative imports only work inside packages — they fail in top-level scripts.
sys.path and Module Discovery
Python searches for modules in this order:
- The directory of the script being run
PYTHONPATHenvironment variable directories- Standard library directories
site-packages(where pip installs packages)
import sys
print(sys.path)
# Add a directory at runtime (avoid in production — use proper packaging)
sys.path.insert(0, "/path/to/my/libs")
pip: Installing Packages
# Install latest version
pip install requests
# Install pinned version
pip install "requests==2.31.0"
# Install from requirements file
pip install -r requirements.txt
# Upgrade
pip install --upgrade requests
# Show what's installed
pip list
pip show requests
# Search PyPI
pip index versions requests
# Uninstall
pip uninstall requests
requirements.txt Best Practices
Pin exact versions for reproducibility in production:
# requirements.txt
requests==2.31.0
fastapi==0.110.0
pydantic==2.5.0
uvicorn==0.27.0
Use ranges only when you’re publishing a library (not an app):
# setup.cfg or pyproject.toml for a library
requests>=2.28,<3.0
Virtual Environments
Every project should have its own isolated environment:
# Create
python -m venv .venv
# Activate
source .venv/bin/activate # macOS/Linux
.venv\Scripts\activate # Windows
# Deactivate
deactivate
Modern Tooling: uv
uv is a drop-in replacement for pip + venv, written in Rust, 10–100x faster:
# Install uv
pip install uv
# Create venv and install
uv venv
uv pip install -r requirements.txt
# Or use uv's project management
uv init my-project
uv add requests fastapi
uv run python main.py
pyproject.toml (Modern Packaging)
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-app"
version = "1.0.0"
description = "A sample application"
requires-python = ">=3.11"
dependencies = [
"requests>=2.31",
"fastapi>=0.110",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"ruff>=0.3",
"mypy>=1.8",
]
[tool.ruff]
line-length = 88
[tool.mypy]
strict = true
Install dev dependencies:
pip install -e ".[dev]"
Useful Standard Library Modules
import os # file system, environment variables
import sys # interpreter, argv, path
import pathlib # object-oriented file paths
import json # JSON encode/decode
import re # regular expressions
import datetime # dates and times
import collections # Counter, defaultdict, deque, namedtuple
import itertools # combinatorial generators
import functools # lru_cache, partial, reduce
import contextlib # context manager helpers
import logging # production logging
import unittest # test framework
import subprocess # run shell commands
import threading # threads
import multiprocessing # processes
import asyncio # async I/O
import socket # networking
import http.client # HTTP
import urllib.parse # URL manipulation
import hashlib # SHA/MD5 hashing
import hmac # HMAC authentication
import secrets # cryptographic random
import tempfile # temporary files
import shutil # file operations (copy, move, rmtree)
Lazy Imports for Performance
Large imports slow down startup. Import inside functions when the module is only sometimes needed:
def export_to_excel(data):
import openpyxl # only imported when this function is called
wb = openpyxl.Workbook()
...
Python caches modules in sys.modules after the first import, so subsequent calls are free.