Why this matters
One file per module is fine for a handful of modules, but real projects (and every library you will install) have dozens or hundreds. Packages let you group modules into directories with a hierarchy of names, such as email.mime.text or xml.etree.ElementTree. Understanding packages explains the dotted names you see in imports, what that mysterious __init__.py is for, why relative imports sometimes fail, what python3 -m really does, and where pip install puts things.
A package is a directory of modules
A package is a directory containing an __init__.py file. Its modules are called submodules:
project/
main.py
shop/
__init__.py
prices.py
cart.py
# shop/prices.py
PRICES = {"apple": 0.5, "bread": 2.25}
def price_of(item):
return PRICES[item]
# main.py
import shop.prices
print(shop.prices.price_of("bread"))
2.25
What happened when import shop.prices ran:
- Python found the directory
shoponsys.path(main.py's directory) with an__init__.pyin it. - It created a module object for the package
shopand executedshop/__init__.pyin it. Packages are modules too; their code is the__init__.pyfile. - It then found
prices.pyinside the package's directory (packages have a__path__attribute listing where to look for submodules), created the moduleshop.prices, and ran it. - It set the attribute
priceson theshopmodule object to the submodule, stored both insys.modules(as"shop"and"shop.prices"), and bound the nameshopin main.
So shop.prices.price_of is a chain of attribute lookups: module shop, its attribute prices (a module), its attribute price_of (a function).
The other import forms work as you would expect:
from shop import prices # binds prices -> the submodule
from shop.prices import price_of # binds price_of -> the function
import shop.prices as sp # binds sp -> the submodule
Note:
import shopalone runs onlyshop/__init__.py. It does not automatically import the submodules, soshop.priceswill raiseAttributeErrorunless something (you, or__init__.py) imported it.
Packages nest: shop/payments/__init__.py makes shop.payments a subpackage. (A directory without __init__.py can also be imported, as a "namespace package", a feature meant for splitting one package across several directories. Always include __init__.py in your own packages; it makes the intent explicit.)
What goes in init.py
Often nothing at all: an empty file, or just a docstring. But because it runs on import shop, it is the place to define the package's public API, so users do not need to know your internal file layout:
# shop/__init__.py
"""A tiny shop: prices and shopping carts."""
from .prices import price_of
from .cart import Cart
__all__ = ["price_of", "Cart"]
Now from shop import price_of works, and you are free to move price_of to another submodule later without breaking users. __all__ lists the public names: it controls what from shop import * imports and tells readers and tools which names are the supported API. Names starting with _ are, by convention, private.
Absolute and relative imports
Inside a package, a module can import its siblings two ways:
# shop/cart.py
from shop.prices import price_of # absolute: full path from a sys.path root
from .prices import price_of # relative: "prices, in my own package"
from . import prices # the sibling module itself
from ..utils import money # one package up (only inside nested packages)
A leading dot means "relative to the package this module belongs to"; each extra dot goes up one level. Relative imports keep working if you rename the top-level package, and make it obvious that a module is internal. Absolute imports are clearer for readers from outside. Both are fine; PEP 8 prefers absolute imports but accepts explicit relative ones inside packages.
Relative imports only work in a module that knows which package it is in. A module learns that from how it was imported: shop.cart has the package shop. That leads to the most common package error.
Running modules inside packages: python3 -m
If you run a submodule directly as a file:
$ python3 shop/cart.py
Traceback (most recent call last):
File "/home/ada/project/shop/cart.py", line 1, in <module>
from .prices import price_of
ImportError: attempted relative import with no known parent package
the file is executed as the top-level module __main__, with no package, so a leading dot means nothing. Worse, sys.path[0] is now project/shop, so even the absolute import shop would fail.
The fix is the -m switch: run a module by its import name.
$ python3 -m shop.cart
Python puts the current directory on sys.path, imports the package shop normally, and then runs shop/cart.py as __main__ while remembering that it belongs to shop. Relative and absolute imports both work. You have already used -m without thinking about it: python3 -m venv, python3 -m pip, python3 -m unittest, python3 -m json.tool.
If you run a package with -m, Python executes its __main__.py:
textkit/
__init__.py
__main__.py <- run by: python3 -m textkit
core.py
That is how a package becomes a command-line tool.
Third-party packages: site-packages, pip and venv
Packages you did not write live in site-packages directories, which are on sys.path after the standard library. pip is the installer: pip install requests downloads a distribution from the Python Package Index (PyPI) and unpacks its packages into a site-packages directory. Nothing more magical than that: afterwards import requests finds it on sys.path like any other package.
Installing everything into the one system Python causes conflicts: project A needs version 1 of a library, project B needs version 2, and your operating system's own Python tools need whatever they were tested with. (Debian and Fedora now refuse pip install into the system Python for this reason, with an "externally-managed-environment" error.) The answer is a virtual environment:
$ python3 -m venv .venv # create one in the project directory
$ . .venv/bin/activate # put .venv/bin first on PATH (optional)
(.venv) $ pip install requests # installs into .venv/lib/python3.13/site-packages
(.venv) $ python -c "import sys; print(sys.prefix)"
/home/ada/project/.venv
.venv/
bin/python -> link to the real python3.13
bin/pip
bin/activate
lib/python3.13/site-packages/ <- this project's third-party packages
pyvenv.cfg <- tells the interpreter "I am a venv, home is /usr/bin"
When .venv/bin/python starts, it sees pyvenv.cfg next to it and uses the venv's site-packages instead of the system one. One venv per project, never committed to git (it is easily rebuilt from a requirements.txt or pyproject.toml). The learnprog sandbox has no network, so you will not install anything here; the standard library already contains a great deal, as the next two lessons show.
Common mistakes
- Running a package module as a file (
python3 shop/cart.py) and gettingattempted relative import with no known parent package. Usepython3 -m shop.cartfrom the project root. - Expecting
import shopto load submodules. Import them explicitly, or import them in__init__.py. - Naming a package after a standard module (
email/,test/,logging/): the same shadowing problem as single files. - Circular imports through
__init__.py. If__init__.pyimportscart, andcartdoesfrom shop import price_of, thencartruns whileshop/__init__.pyis only half done. Import from the submodule directly (from .prices import price_of) inside the package. - Installing with the system pip. Make a venv.
Recap
- A package is a directory with
__init__.py; its modules are submodules named with dots, likeshop.prices. __init__.pyruns on import of the package and is the place for its public API and__all__.- Relative imports (
from .prices import x) work only in modules imported as part of a package. python3 -m pkg.modruns a module by import name;python3 -m pkgrunspkg/__main__.py.- pip installs packages into site-packages; a venv gives each project its own site-packages.
Going further: run python3 -c "import email, json; print(email.__path__, json.__file__)" to see how standard packages are laid out, and read the Python tutorial chapter "Modules", section 6.4 "Packages".