On this page
Modules and Packages
Modules and Packages
As your programs grow, putting all the code in a single file becomes unmanageable. Python's module and package system lets you split code into logical units, reuse code across projects, and leverage the vast ecosystem of third-party libraries. Understanding how Python finds and loads modules is also essential for debugging import errors.
What Is a Module?
A module is simply a .py file. Any Python file can be imported as a module. When you write import math, Python finds the file math.py (or a compiled equivalent) and makes its contents available under the name math.
# math is a standard library module
import math
print(math.pi) # 3.141592653589793
print(math.e) # 2.718281828459045
print(math.sqrt(144)) # 12.0
print(math.floor(3.7)) # 3
print(math.ceil(3.2)) # 4
print(math.log(100, 10)) # 2.0 — log base 10 of 100
print(math.gcd(48, 18)) # 6 — greatest common divisor
print(math.factorial(10)) # 3628800Import Styles
Python offers several ways to import names:
# 1. Import the entire module — access via module.name
import os
import sys
import json
print(os.getcwd()) # current working directory
print(sys.version) # Python version string
print(sys.platform) # 'linux', 'darwin', 'win32'
# 2. Import specific names — no module prefix needed
from math import sqrt, pi, tau
print(sqrt(16)) # 4.0
print(pi) # 3.141592653589793
# 3. Import with alias — great for long module names
import datetime as dt
import collections as col
now = dt.datetime.now()
counter = col.Counter()
# 4. Import specific name with alias
from pathlib import Path as P
home = P.home()
print(home)
# 5. Import all public names (avoid in production code!)
from math import *
print(sin(pi / 2)) # 1.0 — but pollutes namespacetip type: warning title: "Avoid wildcard imports in production"
from module import *imports all public names into your current namespace, potentially overwriting names that already exist. It makes it impossible to tell where a name came from just by reading the code. Reserve it for interactive exploration in the REPL — never use it in production code.
Creating Your Own Modules
Any .py file is a module. Here is how a project might be organized:
my_project/
├── main.py
├── utils/
│ ├── __init__.py
│ ├── strings.py
│ └── numbers.py
└── models/
├── __init__.py
└── user.pyutils/strings.py:
def slugify(text: str) -> str:
"""Convert a string to a URL-friendly slug."""
return text.lower().strip().replace(" ", "-")
def truncate(text: str, max_len: int = 100, suffix: str = "...") -> str:
"""Truncate text to max_len characters."""
if len(text) <= max_len:
return text
return text[:max_len - len(suffix)] + suffix
def title_case(text: str) -> str:
"""Convert text to title case."""
return " ".join(word.capitalize() for word in text.split())main.py:
from utils.strings import slugify, truncate
title = "Hello World This is a Long Title"
print(slugify(title)) # hello-world-this-is-a-long-title
print(truncate(title, 20)) # Hello World This...The `__init__.py` File
A directory with an __init__.py file is a package. The __init__.py can be empty (just marking the directory as a package) or can export a public API:
# utils/__init__.py — re-export commonly used names
from .strings import slugify, truncate, title_case
from .numbers import clamp, round_to
# Now users can do: from utils import slugify
# instead of: from utils.strings import slugifyThe dot in .strings means "relative import" — import from the current package. This is the correct way to import within a package.
The `if __name__ == "__main__"` Guard
When Python runs a file directly, it sets __name__ to "__main__". When a file is imported as a module, __name__ is set to the module's name. This lets you write code that only runs when the file is executed directly:
# calculator.py
def add(a: float, b: float) -> float:
return a + b
def subtract(a: float, b: float) -> float:
return a - b
def multiply(a: float, b: float) -> float:
return a * b
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
if __name__ == "__main__":
# This block only runs when you execute: python calculator.py
# It does NOT run when another file does: import calculator
print(add(10, 5)) # 15
print(divide(10, 3)) # 3.3333...This pattern is ubiquitous in Python — always add it to scripts that also contain importable functions.
`pip` — Python's Package Installer
pip is the standard package manager for Python. It downloads packages from PyPI (the Python Package Index):
# Install a package
pip install requests
# Install a specific version
pip install requests==2.31.0
# Install with version constraints
pip install "requests>=2.28,<3.0"
# Install from a requirements file
pip install -r requirements.txt
# Uninstall a package
pip uninstall requests
# List installed packages
pip list
# Show info about a specific package
pip show requests
# Freeze current environment to requirements.txt
pip freeze > requirements.txt
# Check for outdated packages
pip list --outdated
# Upgrade a package
pip install --upgrade requestsVirtual Environments
A virtual environment is an isolated Python installation that has its own packages, separate from your system Python. This prevents version conflicts between projects:
# Create a virtual environment in a folder called .venv
python -m venv .venv
# Activate the virtual environment
# On Windows (Command Prompt):
.venv\Scripts\activate.bat
# On Windows (PowerShell):
.venv\Scripts\Activate.ps1
# On macOS/Linux:
source .venv/bin/activate
# You'll see (.venv) in your prompt
(.venv) $ pip install requests fastapi
# Deactivate when done
deactivate
# Create requirements.txt from current environment
pip freeze > requirements.txt
# On a new machine, recreate the environment
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txttip type: tip title: "Always use virtual environments"
Never install packages globally (without a virtual environment) for project work. Always create a
.venvinside your project folder and activate it. This way, each project has its own isolated set of dependencies and you avoid the notorious "it works on my machine" problem. Add.venv/to your.gitignore.
Useful Standard Library Modules
Python's standard library is enormous. Here are the modules you will use most often:
`os` and `pathlib`
import os
from pathlib import Path
# pathlib (modern, object-oriented — preferred)
cwd = Path.cwd()
home = Path.home()
config = home / ".config" / "myapp" / "config.json"
print(config)
print(config.parent) # ~/.config/myapp
print(config.name) # config.json
print(config.stem) # config
print(config.suffix) # .json
print(config.exists()) # True or False
# Create directories
(home / "temp_test_dir").mkdir(parents=True, exist_ok=True)
# List files matching a pattern
py_files = list(Path(".").glob("**/*.py"))`sys`
import sys
print(sys.argv) # command-line arguments
print(sys.path) # where Python looks for modules
print(sys.version_info) # (3, 14, 0, 'final', 0)
# Exit the program
if len(sys.argv) < 2:
print("Usage: script.py <filename>")
sys.exit(1) # non-zero = error`datetime`
from datetime import datetime, date, timedelta, timezone
now = datetime.now(tz=timezone.utc) # always use timezone-aware datetimes
today = date.today()
print(now.isoformat()) # 2025-04-02T14:30:00+00:00
print(today) # 2025-04-02
# Arithmetic
one_week = timedelta(weeks=1)
next_week = today + one_week
print(next_week) # 2025-04-09
# Formatting
print(now.strftime("%B %d, %Y")) # April 02, 2025
# Parsing
deadline = datetime.strptime("2025-12-31", "%Y-%m-%d")
days_left = (deadline.date() - today).days
print(f"{days_left} days until deadline")`random`
import random
# Reproducible randomness (for testing)
random.seed(42)
print(random.randint(1, 100)) # integer between 1 and 100 inclusive
print(random.uniform(0.0, 1.0)) # float between 0.0 and 1.0
print(random.choice(["a", "b", "c"])) # pick one item
print(random.choices(["a", "b", "c"], k=5)) # pick k items with replacement
items = list(range(10))
random.shuffle(items) # shuffle in place
print(items)
sample = random.sample(items, k=3) # pick k unique items
print(sample)`re` — Regular Expressions
import re
text = "Contact us at [email protected] or [email protected]"
# Find all email addresses
emails = re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
print(emails) # ['[email protected]', '[email protected]']
# Replace phone numbers
message = "Call 555-1234 or 555-5678 for info"
cleaned = re.sub(r"\d{3}-\d{4}", "[REDACTED]", message)
print(cleaned) # Call [REDACTED] or [REDACTED] for info
# Validate a pattern
pattern = re.compile(r"^\d{4}-\d{2}-\d{2}$") # YYYY-MM-DD
print(bool(pattern.match("2025-04-02"))) # True
print(bool(pattern.match("April 2"))) # FalsenextSteps
- classes-and-objects
Sign in to track your progress