Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

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__.py is 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.