Definition
A growing Python project always ends up facing the same question: where do you put all this code so nobody gets lost in it? A single file grows fast, until it mixes VAT calculation, PDF generation and client handling into the same lines. A package answers exactly that problem, by giving a folder to whatever belongs together. A module is a .py file; a package is the drawer holding several of them, and sometimes other drawers inside. Here is a small billing project organised this way.
billing/
__init__.py
vat.py
pdf.py
clients/
__init__.py
csv.pyThe import statement follows that tree, turning slashes into dots: what Python finds on disk and what you write in code match one to one.
Python still needs to recognise that folder as a package, rather than just a pile of files. That is the job of an __init__.py file, even an empty one: its mere presence makes the folder importable. From there, the from statement walks down the tree the way a file path does.
from billing.vat import compute
from billing.clients.csv import read
import billing.pdfThe word means two different things
A vocabulary trap catches almost everyone sooner or later. The word "package" covers both the folder you import and the archive you install with pip, and nothing guarantees the two share a name.
| The word "package" | What it points at |
|---|---|
| Import package | A folder of modules, whatever follows the import keyword |
| Distribution package | An archive published on PyPI, whatever pip installs |
The two names often match, never out of obligation. You install pillow and import PIL, you install beautifulsoup4 and import bs4, you install scikit-learn and import sklearn. As long as that double life stays unknown, a ModuleNotFoundError right after a successful install feels absurd: the library just arrived, how can it be missing? It is right there, filed under a different name.
What __init__.py is actually for
This empty file leaves a question hanging: what is it for, if leaving it without a single line for a whole career breaks nothing? Its value shows up the day the package gets users, inside the team or not: it then becomes the front door, meaning what the outside world is allowed to know about the folder.
# billing/__init__.py
from .vat import compute
from .pdf import renderCallers then write from billing import compute without needing to know which internal file the function lives in. The folder's internal layout becomes free again: moving compute elsewhere tomorrow breaks nothing for anyone.
Many developers turn this file into a startup script: reading a configuration, opening a database connection, calling an API from __init__.py. All of that runs on the very first import, hence before the first useful line, including during tests and editor autocompletion. A package that takes two seconds to import almost always hides that kind of code at startup.
A folder is not better than a file
Faced with the option of sorting everything into folders, the temptation is to split early, just to be safe. The opposite actually costs less. A three hundred line file reads perfectly well, while six fifty line files importing one another read badly, and sooner or later end up forming a circular import. The right moment to create a package is not when a file grows long, but when it carries two responsibilities that no longer change at the same pace.
The relative import trap
Once the code is organised into a package, a surprise often waits at launch time. A dot in front of a name means "next to me, inside the same package": from .vat import compute. That spelling works when the module is loaded as a member of the package, and only in that case: running the file directly strips Python of that context, and the import fails with an ImportError.
python billing/pdf.py # attempted relative import with no known parent package
python -m billing.pdf # worksThe fix is to always launch the file through the package that contains it, using the -m option from the parent folder, rather than running it alone from the inside. That is exactly what an entry point declared at packaging time does once the code is installed: nobody needs to think about it anymore.
Frequently asked questions
Is __init__.py still mandatory?
Not since Python 3.3: a folder without that file stays importable, as a namespace package. Creating it anyway remains the better habit, because packaging and type checking tools rely on it, and a forgotten folder stops going unnoticed at the worst possible moment.
The install succeeded, so why does the import fail?
Almost always because installing and running did not happen inside the same virtual environment. The second cause, more common than you might think, is the name: the one that installs and the one that imports often differ, and only the project documentation gives the right one.
Does a package have to be published to be reused?
No. A folder shared between two projects installs perfectly well from a Git repository, with a plain pyproject.toml file or a tool such as poetry, and that is the normal route for code shared inside a team. Publishing on PyPI only serves to reach strangers, with the documentation and version tracking that implies on top.