Lesson 3 of 8
A package is a folder of modules. Once you have a dozen .py files, grouping them into
folders keeps the project navigable.
myproject/
main.py
storage/
__init__.py
json_store.py
csv_store.py
reporting/
__init__.py
summary.py
storage and reporting are packages. Import from them with dots, mirroring the folder
structure:
import storage.json_store
from storage.csv_store import export
from reporting import summary
The dot means "inside" — storage.json_store is json_store.py inside the storage folder.
__init__.py
That file marks a folder as a package. It's usually empty, and that's fine — its presence is the point.
It can also run code when the package is imported, which is commonly used to lift the useful names up to the top level:
from .json_store import load, save
from .csv_store import export
With that in storage/__init__.py, users of your package write:
from storage import load, save
instead of the longer from storage.json_store import load, save. You get a tidy public
interface while keeping the internal file layout free to change.
The leading dot in from .json_store import load is a relative import — "from a module
beside me in this package."
Seeing it work here
Same trick as the last lesson: build the package with file operations, then import it.
import os
import sys
os.makedirs("shapes", exist_ok=True)
with open("shapes/__init__.py", "w") as f:
f.write("from .circle import area\n")
with open("shapes/circle.py", "w") as f:
f.write("PI = 3.14159\n\n")
f.write("def area(radius):\n")
f.write(" return PI * radius ** 2\n")
sys.path.insert(0, "")
import shapes
import shapes.circle
print(shapes.circle.PI)
print(shapes.circle.area(1))
print(shapes.area(1))
os.makedirs(..., exist_ok=True) creates the folder without failing if it already exists.
Notice shapes.area works because __init__.py re-exported it.
Absolute vs relative imports
Inside a package, you can import a sibling two ways:
from storage.json_store import load
from .json_store import load
Absolute imports (the first) are clearer and harder to break — PEP 8 recommends them. Relative imports (the second) are shorter and survive the package being renamed. Both are fine; be consistent within a project.
Structuring a real project
A typical small Python project:
expense-tracker/
README.md
requirements.txt
expenses/
__init__.py
models.py
storage.py
reporting.py
tests/
test_storage.py
main.py
main.py is the entry point that wires things together; expenses/ holds the actual logic;
tests/ mirrors its structure. You'll build exactly this shape in the Module 7 project.
Try it yourself
Create a two-module package and import from it both ways.
Checkpoint
- A package is a folder of modules, marked by
__init__.py. - Import with dots:
package.module, mirroring the folders. __init__.pyis usually empty, but can re-export names for a tidier interface.- A leading dot means a relative import from inside the same package.
- Group by responsibility; keep an entry point separate from the logic.
Questions about this lesson
Log in to ask a question.