π¦ Python Modules and Packages
Real applications are thousands of lines spread across many files. Modules and packages are how Python keeps that manageable β letting you split code into reusable units, tap a huge standard library, and pull in community packages with a single command.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Create a module and import it four different ways
- Explain the module search path and the role of
if __name__ == "__main__" - Use key standard-library modules (
os,datetime,json) - Build a package with
__init__.pyand clean re-exports - Install third-party packages with pip inside a virtual environment
Estimated Time: 40β50 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Structure a small data_validator package and set up an isolated venv.
In This Lesson
Why Modular Code?
Imagine building a house where you had to forge every nail and cast every window from scratch. Construction works because it assembles standardized, pre-made components. Python's modules and packages bring the same efficiency to code, letting you:
- Organize related code into separate files and folders
- Reuse code across many projects
- Hide implementation details behind a clean interface
- Share functionality with the wider Python community
Without this structure, a large backend would be an unnavigable single file. With it, teams split work across modules and each piece stays focused and testable.
Creating & Importing Modules
A module is simply a .py file. Its name is the filename without the extension. Let's build one called calculator.py:
# calculator.py
"""A tiny calculator module with basic arithmetic."""
def add(a, b):
"""Return the sum of a and b."""
return a + b
def divide(a, b):
"""Return a / b, raising on division by zero."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
PI = 3.14159
Now you can import that functionality four different ways, each suited to a different situation:
# 1. Import the whole module (clearest β names stay namespaced)
import calculator
calculator.add(5, 3)
print(calculator.PI)
# 2. Import specific names (concise for a few items)
from calculator import add, PI
add(10, 7)
# 3. Import with an alias (handy for long names, e.g. numpy as np)
import calculator as calc
calc.divide(20, 4)
# 4. Import everything (discouraged β pollutes the namespace)
from calculator import * # avoid: unclear where names come from
β calculator.add()"] A --> C["from calculator import add
β add()"] A --> D["import calculator as calc
β calc.add()"]
β Prefer explicit imports
import calculator or from calculator import add make it obvious where a name comes from. Avoid from module import * in real code β it hides the source of every name and invites collisions.
The Search Path & __main__
When you write import calculator, Python hunts for the module in a specific order, stored in sys.path:
- The directory of the script being run (or the current directory)
- The standard-library directories
- The
site-packagesdirectory (where pip installs third-party packages) - Any directories listed in the
PYTHONPATHenvironment variable
import sys
for path in sys.path:
print(path)
This mirrors how your operating system uses PATH to find executables β it checks a list of locations in order and uses the first match.
The if __name__ == "__main__" guard
Every module has a built-in __name__ variable. When a file is run directly, __name__ is "__main__"; when it is imported, __name__ is the module's name. This lets a file act as both a reusable library and a runnable script:
# greet.py
def greet(name):
return f"Hello, {name}!"
if __name__ == "__main__":
# Runs only when you do `python greet.py`, NOT when imported
print(greet("World"))
π‘ Why it matters
Without the guard, any code at the bottom of a module β like a test print or a server start β would fire the moment another file imports it. The guard keeps "run me" logic separate from "import me" logic.
The Standard Library
Python ships with a rich standard library β "batteries included." These modules come with every install, no download required.
| Module | Purpose | Common use |
|---|---|---|
os / pathlib | Filesystem & OS interface | Paths, files, environment variables |
sys | Interpreter internals | Arguments, exit codes, sys.path |
datetime | Dates & times | Timestamps, durations, formatting |
json | JSON encode/decode | API payloads, config files |
re | Regular expressions | Validation, parsing, search |
collections | Specialized containers | Counter, defaultdict, namedtuple |
A few in action
# Paths done right β pathlib is the modern choice
from pathlib import Path
cwd = Path.cwd()
config = Path("config") / "settings.ini" # OS-independent joining
Path("new_folder").mkdir(exist_ok=True)
# Dates and durations
from datetime import datetime, timedelta
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))
tomorrow = now + timedelta(days=1)
# JSON β the lingua franca of web APIs
import json
user = {"name": "Alice", "age": 28, "active": True}
text = json.dumps(user, indent=2) # Python dict -> JSON string
back = json.loads(text) # JSON string -> Python dict
print(back["name"]) # Alice
The standard library is a giant toolbox that comes free with Python β reach for it before installing anything.
Packages & __init__.py
A package is a directory of modules. Historically it's marked by an __init__.py file (which can be empty). Packages let you group related modules and namespace them hierarchically.
webapp/ # top-level package
__init__.py
config.py
models/ # sub-package
__init__.py
user.py
product.py
controllers/
__init__.py
auth.py
utils/
__init__.py
validation.py
__init__.py marks it as importable and can re-export names for convenience.What __init__.py is for
Beyond marking a folder as a package, __init__.py can run setup code and β most usefully β re-export names so callers get a clean, shallow import path:
# webapp/models/__init__.py
"""Data models for the app."""
from .user import User, Role
from .product import Product
__all__ = ["User", "Role", "Product"] # controls `from models import *`
# Because of the re-exports above, callers can write:
from webapp.models import User, Product
# instead of the deeper:
from webapp.models.user import User
from webapp.models.product import Product
π Relative vs absolute imports
Inside a package, from .user import User is a relative import (the dot means "this package"). From outside, use the absolute path from webapp.models import User. Prefer absolute imports in application code; relative imports are handy within a package's own internals.
pip & Virtual Environments
Python's real superpower is PyPI, a repository of hundreds of thousands of community packages. You install them with pip.
# Install a package
pip install requests
# Pin an exact version
pip install "flask==3.0.0"
# Install everything a project needs
pip install -r requirements.txt
# Snapshot your current dependencies
pip freeze > requirements.txt
Isolate every project with a virtual environment
Different projects need different (often conflicting) package versions. A virtual environment gives each project its own private set of packages β like separate apartments in a building, each furnished independently.
# Create a venv in a folder named .venv
python -m venv .venv
# Activate it
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows
# Now pip installs land inside this project only
pip install django
# When you're done
deactivate
β οΈ Never install into the system Python
Installing packages globally leads to version clashes between projects and can break OS tooling that depends on the system Python. Create and activate a virtual environment first, every time. Add .venv/ to your .gitignore.
Third-party vs standard library β a quick comparison
# Standard library β works everywhere, more verbose
import urllib.request, json
with urllib.request.urlopen("https://api.github.com/users/python") as r:
data = json.loads(r.read().decode())
# The 'requests' package β install once, far cleaner
import requests
data = requests.get("https://api.github.com/users/python").json()
print(data["name"])
Structuring a Real Project
Here is how modules and packages come together in a realistic Flask backend. Notice how each concern lives in its own place:
myapp/
__init__.py # application factory (create_app)
config.py # settings per environment
models/ # database models
routes/ # request handlers (blueprints)
services/ # email, analytics, etc.
templates/ # HTML
static/ # CSS, JS, images
app.py # entry point
requirements.txt # dependencies
# myapp/__init__.py β the "application factory" pattern
from flask import Flask
from . import config
def create_app(config_name="default"):
"""Build and configure the Flask app, then return it."""
app = Flask(__name__)
app.config.from_object(getattr(config, f"{config_name.capitalize()}Config"))
# Register route blueprints
from .routes import auth, blog
app.register_blueprint(auth.bp)
app.register_blueprint(blog.bp)
return app
# app.py β the entry point
from myapp import create_app
app = create_app()
if __name__ == "__main__":
app.run(debug=True)
β Why this structure wins
- Each part is small, focused, and easy to locate.
- Multiple developers can work on different sub-packages without collisions.
- Isolated modules are far easier to unit-test.
- Code is portable β a well-factored
utilspackage drops into the next project.
Hands-on Exercise
ποΈ Build a data_validator Package
Objective: Turn loose validation functions into a proper, importable package inside a virtual environment.
Instructions:
- Create and activate a virtual environment (
python -m venv .venv). - Make this folder tree:
data_validator/ __init__.py email.py password.py - In
email.py, writevalidate_email(addr)using theremodule; inpassword.py, writepassword_strength(pw). - In
__init__.py, re-export both so callers can dofrom data_validator import validate_email. - Write a
demo.pybeside the package that imports and exercises it.
π‘ Hint
A serviceable email regex is r"^[^@\s]+@[^@\s]+\.[^@\s]+$". In __init__.py, use relative imports: from .email import validate_email. Run the demo from the folder containing data_validator/ so the package is on the search path.
β Sample solution
# data_validator/email.py
import re
_EMAIL = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
def validate_email(addr):
"""Return True if addr looks like a valid email address."""
return isinstance(addr, str) and bool(_EMAIL.match(addr.strip()))
# data_validator/password.py
def password_strength(pw):
"""Return a 0β4 score based on simple strength rules."""
checks = [
len(pw) >= 8,
any(c.isupper() for c in pw),
any(c.isdigit() for c in pw),
any(not c.isalnum() for c in pw),
]
return sum(checks)
# data_validator/__init__.py
"""Reusable validators for web input."""
from .email import validate_email
from .password import password_strength
__all__ = ["validate_email", "password_strength"]
# demo.py
from data_validator import validate_email, password_strength
print(validate_email("user@example.com")) # True
print(password_strength("Secret123!")) # 4
π― Quick Quiz
Question 1: What makes a directory a Python package in the traditional sense?
Question 2: When you run python greet.py directly, what is the value of __name__ in that file?
Question 3: Why should each project use its own virtual environment?
Summary & Quiz
π Key Takeaways
- A module is a
.pyfile; import it whole, selectively, or with an alias β avoidimport *. - Python finds modules via sys.path; the
if __name__ == "__main__"guard separates "run me" from "import me". - The standard library (os/pathlib, datetime, json, re, collections) covers most everyday needs with zero installs.
- A package is a folder of modules;
__init__.pymarks it and can re-export a clean public API. - Install third-party packages with pip inside a per-project virtual environment.
π Further Reading
- Python Tutorial β Modules & Packages
- The Python Standard Library
- Python Packaging User Guide
- PyPI β the Python Package Index
π What's Next?
You've now covered Python end to end β syntax, control flow, and organization. Next we switch languages to see the same backend ideas in a different light, starting with PHP syntax and variables.
π Well organized!
You can now split, share, and reuse Python code like a pro. On to PHP.