Packages and __init__.py
18 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Once a program has more than one file, imports decide how tidy it feels. A package is the standard way to group files, so other code can import them by one name.
A folder you can import
Package is a folder of Python modules that you can import by the folder name. It normally holds an __init__.py file, which runs when the package is first imported.
A module is one .py file. A package is a folder of modules that you import by the folder's name. The dots in an import follow the folders down to the file.
shop-kit/ pyproject.toml src/ shopkit/ __init__.py pricing.py __main__.pyIn from shopkit.pricing import total_with_tax, shopkit is the folder, pricing is the file pricing.py, and total_with_tax is a name inside it.
What __init__.py does
__init__.py is the file Python runs when a package is first imported. It can be empty, or it can import names so users get a short public path.
Python runs __init__.py once, the first time anyone imports the package. It can be empty. Most often it pulls a few names up, so users write shopkit.total_with_tax instead of the long path.
"""Small helpers for shop calculations."""from .pricing import total_with_tax
__all__ = ["total_with_tax"]__version__ = "0.1.0"Example 1 · A package built and imported
import sysimport tempfilefrom pathlib import Path
root = Path(tempfile.mkdtemp())pkg = root / "shopkit"pkg.mkdir()(pkg / "pricing.py").write_text("""def total_with_tax(price, rate=18): return round(price + price * rate / 100, 2)""")(pkg / "__init__.py").write_text("""print("shopkit is loading")from .pricing import total_with_tax""")sys.path.insert(0, str(root))
import shopkitimport shopkitprint(shopkit.total_with_tax(2000))shopkit is loading 2360.0
What this code does
- The code makes the folder and two files, then imports
shopkit. __init__.pyruns once. The secondimportfinds the package already loaded, so the message appears one time.
Example 2 · __all__ and star imports
import sysimport tempfilefrom pathlib import Path
root = Path(tempfile.mkdtemp())pkg = root / "shopkit"pkg.mkdir()(pkg / "pricing.py").write_text("""def total_with_tax(price): return price
def shipping(price): return 40""")(pkg / "__init__.py").write_text("""from .pricing import total_with_tax, shipping__all__ = ["total_with_tax"]""")sys.path.insert(0, str(root))
names = {}exec("from shopkit import *", names)print(sorted(key for key in names if not key.startswith("__")))['total_with_tax']
What this code does
- Both functions are imported into the package, but only the one in
__all__comes through a star import. shopkit.shippingis still reachable by its full name.__all__only controlsimport *.
__all__ lists the names that from shopkit import * brings in. __version__ is a common place to keep the version string that code can read.
Absolute and relative imports
Relative import is an import that starts with a dot, such as from .pricing import x. The dot means the package that the current file lives in.
Inside a package, one file can import another in two ways. An absolute import starts at the top package. A relative import starts with a dot and means "this package".
# in shopkit/reports.pyfrom shopkit.pricing import total_with_tax # absolutefrom .pricing import total_with_tax # relativeExample 3 · Relative imports need a package
import runpyimport sysimport tempfilefrom pathlib import Path
root = Path(tempfile.mkdtemp())pkg = root / "shopkit"pkg.mkdir()(pkg / "__init__.py").write_text("")(pkg / "pricing.py").write_text("""def total_with_tax(price, rate=18): return round(price + price * rate / 100, 2)""")(pkg / "reports.py").write_text("""from .pricing import total_with_tax
def line(price): return f"Total: {total_with_tax(price)}"""")sys.path.insert(0, str(root))
from shopkit import reportsprint(reports.line(1000))try: runpy.run_path(str(pkg / "reports.py"))except ImportError as error: print(error)Total: 1180.0 attempted relative import with no known parent package
What this code does
- Imported as
shopkit.reports, the file finds.pricingwithout trouble. - Run directly as a script, it has no parent package, so the relative import fails with an
ImportError.
Both work when the file is imported as part of the package. A relative import fails if you run the file directly as a script, because the file then has no parent package. Most style guides prefer absolute imports.
Running a package with -m
A package becomes a program when it has a __main__.py file. Run it with -m and the package name, from the folder that holds the package. The -m form sets up the package, so relative imports work.
python -m shopkit2360.0
Example 4 · Run the package
Runs in: Your terminal
python -m shopkit2360.0
What this code does
-m shopkitrunsshopkit/__main__.pywith the package set up.- It is the standard way to give a package a command-line entry point.
Here __main__.py calls total_with_tax(2000) and prints the result.
The src layout and an editable install
Editable install is pip install -e ., which links your project into the environment instead of copying it. Changes to the code take effect without installing again.
Put the package inside a src folder, as in the tree above. Python then cannot import it by accident from the current folder, so you test the installed copy. pip install -e . installs it as an editable link.
python -m pip install -e .Obtaining file:///home/priya/shop-kit Installing build dependencies ... done Preparing editable metadata (pyproject.toml) ... done Building editable for shopkit (pyproject.toml) ... done Successfully built shopkit Installing collected packages: shopkit Successfully installed shopkit-0.1.0
Run the command in the folder that holds pyproject.toml, with your virtual environment active. Edits to the code show up at once, with no reinstall.
Next step
Check your understanding
8 quick questions