Skip to content

Week 4 - Modules, Packaging, Virtual Environments, and the Import System

4.1 Conceptual Core

  • A module is a .py file (or .so / .pyd extension) that becomes a singleton object on first import, cached in sys.modules. Re-importing returns the cached object; importlib.reload re-executes (with caveats - old references to old objects persist).
  • A package is a directory with an __init__.py (or a namespace package, PEP 420, with no __init__.py).
  • Virtual environments are not optional. A modern Python project lives in a per-project .venv/, managed by uv, hatch, poetry, or pip-tools. System Python is for the OS, not your code.

4.2 Mechanical Detail

  • Import resolution order: sys.modules cache → finders in sys.meta_path → loaders. The default finders are BuiltinImporter, FrozenImporter, PathFinder (which searches sys.path).
  • Absolute vs. relative imports (from . import sibling, from ..pkg import x). Prefer absolute.
  • __main__: python -m mypkg runs mypkg/__main__.py as __main__. The if __name__ == "__main__": idiom exists because a module imported as a library has a different __name__ than one run as a script.
  • pyproject.toml (PEP 517, 518, 621, 660): the single source of truth for project metadata, build backend, dependencies, and tool configuration. setup.py is dead for new projects.
  • Build backends: hatchling, setuptools, flit-core, poetry-core, maturin (for Rust extensions), scikit-build-core (for C/C++/CMake).
  • Dependency resolution: pip (legacy, slow), uv (fast, Rust, drop-in pip replacement and resolver), poetry (lockfile-first). The curriculum standardizes on uv for speed and ecosystem direction.

4.3 Lab - "Ship a CLI"

  1. Build a CLI tool - e.g., a Markdown table of contents generator. Project layout: src/toctool/{__init__.py,__main__.py,cli.py,core.py}, tests/, pyproject.toml.
  2. Configure [project.scripts] toctool = "toctool.cli:main". Verify pipx install . makes toctool available system-wide.
  3. Add a [project.optional-dependencies] dev = [...] group. uv sync --extra dev installs the dev tools.
  4. Tag v0.1.0. Build wheel + sdist with uv build. Inspect the wheel with unzip -l. Confirm no test files leaked in.
  5. (Optional, sets up later weeks) Publish to TestPyPI.

4.4 Idiomatic & Linter Drill

  • Enable ruff rule set TID (banned-imports), INP (implicit namespace packages). Configure your __init__.py to re-export a curated public API (__all__).

4.5 Production Hardening Slice

  • Add a pre-commit config running ruff check, ruff format, pyright, and pytest -x. Add a GitHub Actions (or equivalent) CI workflow that runs make check on push and matrix-tests over Python 3.12 and 3.13.

Month-1 Exit Criteria

Before starting Month 2, the reader should be able to, on a whiteboard:

  1. Diagram the namespace lookup order for a name in a function inside a class inside a module (LEGB, with the class scope wrinkle).
  2. Explain the difference between is, ==, and __eq__.
  3. Write a generator pipeline that processes a 100GB log file in constant memory.
  4. Bootstrap a publishable Python package with uv, ruff, pyright, pytest, and CI in under 30 minutes.

Comments