Python Modules and Packages: The Complete Beginner's Guide

This chapter shows how to scale beyond one file using modules and packages, master import patterns, use the standard library, install third-party packages with pip, and isolate dependencies with virtual environments.

Chapter 13 of 20 · Intermediate · 40 min · Python Programming Course

Every Python program you have written so far can live in one file. Real projects cannot. Modules and packages let you split code into reusable, maintainable pieces.

What You Will Learn in This Chapter

By the end of this tutorial you will be able to:

  • Import modules with import, from ... import, and aliases
  • Use core standard-library modules effectively
  • Create and import your own modules
  • Understand module lookup using sys.path
  • Build packages with __init__.py
  • Install third-party packages with pip
  • Use virtual environments for dependency isolation
  • Avoid common import pitfalls

Estimated time: 40 minutes reading + 20 minutes practice

The Three Import Styles

1) import module

import math
print(math.sqrt(16))

2) from module import name

from math import sqrt, pi
print(sqrt(25), pi)

3) import module as alias

import datetime as dt
print(dt.date.today())

Avoid wildcard imports:

from math import *   # avoid

Standard Library Essentials

math

import math
print(math.pi, math.sqrt(144), math.factorial(6))

random

import random
random.seed(42)
print(random.randint(1, 100))

datetime

from datetime import datetime, timedelta
now = datetime.now()
print(now + timedelta(days=1))

os

import os
print(os.getcwd())
print(os.path.join("data", "output.csv"))

sys

import sys
print(sys.version)
print(sys.path)

json

import json
data = {"name": "Alice", "age": 25}
raw = json.dumps(data, indent=2)
print(json.loads(raw)["name"])

collections

from collections import Counter, defaultdict, deque
print(Counter(["python", "python", "java"]))

Creating Your Own Module

Any .py file is a module.

mathutils.py:

PI = 3.14159265358979

def circle_area(radius):
    return PI * radius ** 2

main.py:

import mathutils
print(mathutils.circle_area(5))

How Python Finds Modules

Python searches import locations in order including current directory, environment paths, stdlib, and site-packages.

import sys
print(sys.path)

__name__ Guard

def circle_area(radius):
    return 3.14159 * radius ** 2

if __name__ == "__main__":
    print(circle_area(5))

Creating Packages

A package is a directory with modules and typically __init__.py.

# schoolabe/__init__.py
from .math_tools import add
__version__ = "1.0.0"

Installing Third-Party Packages with pip

pip install requests
pip freeze > requirements.txt
pip install -r requirements.txt

Virtual Environments

python -m venv venv
source venv/bin/activate
pip install requests
deactivate

Use a virtual environment per project to prevent version conflicts.

A Complete Working Program

Below is a compact report utility that combines standard library modules:

import math
import json
import random
from datetime import datetime, timedelta
from collections import Counter, defaultdict

random.seed(42)

def generate_sales_data(n=20):
    products = ["Python", "Kafka", "DSA", "JS"]
    records = []
    start = datetime(2026, 1, 1)
    for i in range(n):
        records.append({
            "id": f"TXN{i+1:04d}",
            "date": (start + timedelta(days=random.randint(0, 60))).strftime("%Y-%m-%d"),
            "product": random.choice(products),
            "amount": round(random.uniform(30, 300), 2),
        })
    return records

def report(records):
    amounts = [r["amount"] for r in records]
    totals = defaultdict(float)
    for r in records:
        totals[r["product"]] += r["amount"]
    return {
        "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "count": len(records),
        "total": round(sum(amounts), 2),
        "mean": round(sum(amounts) / len(amounts), 2),
        "std_dev": round(math.sqrt(sum((x - (sum(amounts)/len(amounts)))**2 for x in amounts)/len(amounts)), 2),
        "top_products": Counter(r["product"] for r in records).most_common(3),
        "revenue_by_product": dict(sorted(totals.items(), key=lambda x: x[1], reverse=True)),
    }

5 Import Mistakes Every Beginner Makes

  1. Naming your file like stdlib modules (random.py, json.py)
  2. Circular imports between modules
  3. Wildcard imports (from x import *)
  4. Missing or misusing package __init__.py
  5. Installing globally instead of inside a virtual environment

Practice Exercises

  • Standard library mini report with math, random, datetime
  • Build a custom text_utils.py and import it from main.py
  • JSON write/read workflow for student records
  • os-based Python file listing utility
  • collections usage with Counter, defaultdict, deque

-> See all Python practice exercises with solutions

What Comes Next - Day 14: File Handling

Day 14 moves from code organisation to persistent data:

  • opening/closing files safely
  • reading/writing/appending content
  • CSV handling
  • path operations with pathlib
  • safe file error handling

-> Continue to Day 14: File Handling

Frequently Asked Questions

What is a module in Python?

A module is any .py file containing Python code.

What is the difference between a module and a package?

A module is one file. A package is a directory of modules.

What is the difference between import styles?

import x keeps namespace explicit, from x import y imports directly, aliases shorten long names.

What does if __name__ == "__main__" mean?

It runs code only when the file is executed directly, not when imported.

What is pip in Python?

pip installs Python packages from PyPI and manages dependencies.

What is a virtual environment in Python?

An isolated environment for project-specific dependencies.

What is __init__.py in a package?

A package initializer that marks package boundaries and controls exports.

What is sys.path in Python?

A list of paths Python searches during import resolution.

Chapter navigation

Controlling Package Exports with __all__

__all__ is a list of public names exported when someone uses from mypackage import *. It also signals to developers what the intended public API is.

# mylib/utils.py
__all__ = ['format_date', 'parse_date']   # Only these are "public"

def format_date(d): ...
def parse_date(s): ...
def _internal_helper(): ...   # Leading underscore = private convention

Essential Standard Library Deep Dive

collections — Specialized Data Structures

from collections import Counter, defaultdict, deque, namedtuple, OrderedDict

# Counter: count occurrences
words = "apple banana apple cherry banana apple".split()
freq = Counter(words)
print(freq)                   # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(freq.most_common(2))    # [('apple', 3), ('banana', 2)]
freq.update(['apple', 'date'])

# defaultdict: auto-initialize missing keys
from collections import defaultdict
graph = defaultdict(list)
graph['A'].append('B')    # No KeyError — creates [] automatically
graph['A'].append('C')

# deque: O(1) append/pop from both ends
from collections import deque
queue = deque(['a', 'b', 'c'], maxlen=5)
queue.appendleft('z')     # O(1) — unlike list insert at 0
queue.append('d')
queue.rotate(1)            # Rotate right by 1

# namedtuple: lightweight record type
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x, p.y)           # 3 4
print(p._asdict())         # {'x': 3, 'y': 4}

itertools — Combinatorial and Infinite Iterators

import itertools

# Infinite iterators
for i in itertools.count(10, 2):   # 10, 12, 14, ...
    if i > 20: break

# Combinatorics
list(itertools.combinations('ABC', 2))    # [('A','B'), ('A','C'), ('B','C')]
list(itertools.permutations('ABC', 2))    # All 2-permutations
list(itertools.product([0,1], repeat=3))  # All 3-bit binary strings

# Chaining and grouping
list(itertools.chain([1,2], [3,4], [5]))  # [1, 2, 3, 4, 5]

for key, group in itertools.groupby("AAABBBCCAA"):
    print(key, list(group))   # A [A,A,A], B [B,B,B], C [C,C], A [A,A]

# accumulate — running totals
list(itertools.accumulate([1,2,3,4,5]))           # [1, 3, 6, 10, 15]
list(itertools.accumulate([1,2,3,4,5], max))      # [1, 2, 3, 4, 5]

functools — Higher-Order Functions

import functools

# lru_cache — memoize function results
@functools.lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2: return n
    return fibonacci(n-1) + fibonacci(n-2)

fibonacci(100)  # Instant with caching

# partial — pre-fill arguments
from functools import partial
double = partial(pow, exp=2)
triple_add = partial(lambda a, b, c: a+b+c, c=0)  # Fix last arg

# reduce — fold a sequence
product = functools.reduce(lambda a, b: a * b, [1, 2, 3, 4, 5])  # 120

pathlib — Modern Path Operations

from pathlib import Path

# All path operations are object methods — no string concatenation
home = Path.home()
project = home / 'projects' / 'myapp'     # / operator joins paths!

config = project / 'config.json'
print(config.exists())                     # True/False
print(config.stem)                         # 'config'
print(config.suffix)                       # '.json'
print(config.parent)                       # ~/projects/myapp

# Glob patterns
for py_file in project.glob('**/*.py'):    # Recursive
    print(py_file.name)

# Read/write
config.write_text('{"debug": true}')
data = config.read_text()

Relative Imports in Packages

Within a package, prefer relative imports over absolute ones for internal modules:

# myapp/utils/validators.py
from ..models import User          # Two levels up, then into models
from .helpers import format_email  # Same package (utils)
from myapp.config import SETTINGS  # Absolute import — also fine

Rule: Relative imports only work inside packages (not scripts run directly).

datetime and time — Working with Dates

from datetime import datetime, date, timedelta, timezone

# Current time
now = datetime.now()                           # Local time (no timezone info)
utc_now = datetime.now(tz=timezone.utc)       # UTC with timezone info

# Creating dates
birthday = date(1995, 6, 15)
meeting = datetime(2026, 7, 1, 14, 30, 0)

# Arithmetic
tomorrow = date.today() + timedelta(days=1)
two_weeks = now + timedelta(weeks=2)
diff = datetime(2026, 12, 31) - now            # timedelta object
print(f"{diff.days} days until year end")

# Formatting and parsing
formatted = now.strftime("%Y-%m-%d %H:%M:%S")  # "2026-06-22 10:30:00"
parsed = datetime.strptime("22/06/2026", "%d/%m/%Y")

# ISO format (recommended for APIs/files)
iso = now.isoformat()                          # "2026-06-22T10:30:00"
back = datetime.fromisoformat(iso)

json — Serialization Patterns

import json
from datetime import datetime

# Custom encoder for types json doesn't know about
class AppEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, set):
            return sorted(obj)  # Sets → sorted lists
        if hasattr(obj, '__dict__'):
            return obj.__dict__  # Simple objects → dict
        return super().default(obj)

data = {"created": datetime.now(), "tags": {"python", "web"}}
serialized = json.dumps(data, cls=AppEncoder, indent=2)

# Read/write files safely
def load_json(path, default=None):
    try:
        with open(path, encoding='utf-8') as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return default

def save_json(path, data, indent=2):
    with open(path, 'w', encoding='utf-8') as f:
        json.dump(data, f, cls=AppEncoder, indent=indent, ensure_ascii=False)

os and sys — System Interaction

import os, sys

# Environment variables
db_url = os.environ.get('DATABASE_URL', 'sqlite:///local.db')
debug = os.getenv('DEBUG', 'false').lower() == 'true'

# Process info
pid = os.getpid()
cwd = os.getcwd()

# sys — interpreter info
print(sys.version)        # Python version string
print(sys.platform)       # 'linux', 'darwin', 'win32'
print(sys.argv)           # Command-line arguments: [script_name, arg1, arg2, ...]

# sys.path manipulation (use sparingly)
sys.path.insert(0, '/path/to/custom/lib')  # Add at front for priority

# Exit with status code
if not os.path.exists('required_config.json'):
    print("ERROR: required_config.json missing", file=sys.stderr)
    sys.exit(1)  # Non-zero = failure

Creating a Professional Python Package

my_library/
├── pyproject.toml        ← Modern package config (replaces setup.py)
├── README.md
├── src/
│   └── my_library/
│       ├── __init__.py   ← Public API
│       ├── core.py
│       ├── utils.py
│       └── _internal.py  ← Private (underscore prefix)
└── tests/
    └── test_core.py
# pyproject.toml (minimal)
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.backends.legacy:build"

[project]
name = "my-library"
version = "1.0.0"
description = "A short description"
requires-python = ">=3.10"
dependencies = ["requests>=2.28"]

[project.optional-dependencies]
dev = ["pytest", "black", "mypy"]
# src/my_library/__init__.py
"""Expose only the public API."""
from .core import MyClass, important_function
from .utils import helper

__all__ = ['MyClass', 'important_function', 'helper']
__version__ = '1.0.0'
# Install locally in editable mode for development
pip install -e ".[dev]"

# Build distribution
pip install build
python -m build  # Creates dist/*.whl and dist/*.tar.gz

# Publish to PyPI
pip install twine
twine upload dist/*

Summary Cheatsheet

TaskTool
Import module`import math` / `from math import sqrt`
Create packageDirectory + `__init__.py`
Install package`pip install requests`
Isolated environment`python -m venv .venv`
Counting items`collections.Counter`
Auto-init missing keys`collections.defaultdict`
Double-ended queue`collections.deque`
Memoize function`@functools.lru_cache`
Generate combinations`itertools.combinations`
Modern file paths`pathlib.Path`
Date arithmetic`datetime.timedelta`
JSON read/write`json.load` / `json.dump`
Env variable`os.environ.get('KEY', 'default')`
Script args`sys.argv`

Frequently asked questions: Modules and packages

What is a module in Python?

A module is any .py file containing Python code such as functions, classes, and variables.

What is the difference between a module and a package?

A module is a single file. A package is a directory containing related modules.

What is the difference between import math and from math import sqrt?

import math keeps namespaced access (math.sqrt). from math import sqrt imports a specific name directly (sqrt).

What does if __name__ == "__main__": mean in Python?

It runs code only when a file is executed directly, not when imported as a module.

What is pip in Python?

pip is Python’s package installer used to install, upgrade, and manage third-party packages.

What is a virtual environment in Python?

A virtual environment isolates project dependencies so package versions do not conflict across projects.

What is __init__.py in a Python package?

__init__.py marks package boundaries and can control which names are exported at package level.

What is sys.path in Python?

sys.path lists directories Python searches when resolving imports.