| Java | Python |
|---|---|
.java file / class |
module: any .py file |
| package (directory) | package: a directory with an __init__.py |
| JAR on Maven Central | distribution on PyPI (pypi.org), installed with uv/pip |
pom.xml / build.gradle |
pyproject.toml |
| Maven/Gradle | uv (or pip, poetry) |
~/.m2 + classpath |
virtual environment (.venv/), one per project |
mvn dependency:tree lock |
uv.lock (commit it!) |
One file can hold many classes and functions. Don't write one class per file. Group by topic.
import math # use as math.sqrt(2)
from math import sqrt, pi # use as sqrt(2)
from collections import Counter as C # alias
import numpy as np # conventional aliases: np, pd, plt
from mypkg.models import Note # absolute import (preferred)
from .models import Note # relative import, only inside a packageAvoid from module import *. It hides where names come from.
When you import x, Python searches sys.path (≈ the classpath): the script's directory, then
the standard library, then .venv/.../site-packages. Modules run once, on first import, and are then cached in sys.modules.
That's why the if __name__ == "__main__": guard matters.
notes_lite/
├── __init__.py # runs on `import notes_lite`; defines the public API
├── __main__.py # runs on `python -m notes_lite`
├── models.py # Note, Priority
├── store.py # NoteStore (imports from .models)
└── exporters.py # MarkdownExporter, CsvExporter
__init__.py usually re-exports the public API so users write from notes_lite import Note
instead of from notes_lite.models import Note:
# notes_lite/__init__.py
from .models import Note, Priority
from .store import NoteStore
__all__ = ["Note", "Priority", "NoteStore"] # what's "public" (and what `import *` takes)
__version__ = "0.1.0"Circular imports (a imports b, b imports a) fail more easily than in Java. Keep dependencies
pointing one way: models ← store ← exporters/cli.
my-project/
├── pyproject.toml
├── uv.lock
├── src/notes/__init__.py # the installable package (you build this in the Lesson 16 capstone)
├── tests/
└── .venv/
Putting the package under src/ stops it from being imported by accident from the working directory.
You only import the installed version, which uv installs in editable mode (≈ mvn install with live reload).
[project]
name = "notes"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = ["httpx>=0.28", "pydantic>=2.13"] # ≈ <dependencies>
[project.scripts]
notes = "notes:main" # creates a `notes` command → calls notes.main()
[dependency-groups]
dev = ["pytest", "ruff", "mypy"] # ≈ <scope>test</scope>| Task | Command |
|---|---|
| add a dependency | uv add httpx |
| add a dev-only dependency | uv add --dev pytest |
| remove | uv remove httpx |
| install everything from lock | uv sync |
| run in the venv | uv run python app.py, uv run pytest |
| run a tool without installing | uvx ruff check . (≈ npx) |
| upgrade | uv lock --upgrade |
You'll see pip install -r requirements.txt in older projects. It's the same idea with less tooling.
pathlib, json, csv, datetime, re, logging, argparse, subprocess, itertools,
functools, collections, dataclasses, typing, unittest, sqlite3, urllib, zipfile,
random, statistics, uuid, hashlib. Check the stdlib before adding a dependency.
cd lessons/05_packages/demo
uv run python app.py
uv run python -m geometryTurn your Lesson 04 solution into a package. Work in lessons/05_packages/exercise/:
- Fill in the files in
notes_lite/. Each file has TODO instructions. Copy your Lesson 04 code into them. - Use relative imports between modules (
from .models import Note). - Re-export the public API from
__init__.py, and set__all__and__version__ = "0.1.0". - Make
python -m notes_liteprintnotes_lite 0.1.0: 3 notes.
Check your work:
cd lessons/05_packages/exercise
uv run python check.py