A Python package is a folder of modules with an __init__.py file, and the layout you choose decides how other people - including future you - import your code. This guide builds one small package from scratch and shows exactly which file each import statement needs, so you stop guessing why from shop.cart import add_item fails.
What does __init__.py actually do?
Historically __init__.py was mandatory and usually empty. Since Python 3.3 a folder without it can still be imported as a namespace package, but that is an edge case: tools, build systems and IDEs all assume the regular layout, so create __init__.py every time.
Three things make it worth the effort. First, the file runs whenever the package is imported, which makes it the right place for package-level setup. Second, it gives the package a public face: re-export the names other modules should use, and hide the rest. Third, __all__ inside it defines exactly what from shop import * exposes.
That public face is what makes import shop pleasant to use. Without the re-exports in __init__.py, a caller would have to know that cart_total lives in cart.py and write from shop.cart import cart_total. With them, the package tells its users what it offers.
The discipline that keeps packages healthy is to keep __init__.py light. Every name you import there is executed on every single import of the package, including imports that needed only one small helper. Heavy work in __init__.py slows down startup and makes circular imports much easier to create.
What does a well-structured package look like?
Here is a complete, runnable package. Four files, each with one job: the catalogue of prices and the tax rules, the basket operations, the package's public API, and an entry point.
shop/
├── __init__.py # the public API of the package
├── __main__.py # runs when you type: python -m shop
├── cart.py # basket operations
└── pricing.py # catalogue, tax rate and discounts
# shop/pricing.py
"""Price rules: the catalogue, the tax rate and the discount table."""
TAX_RATE = 0.2
CATALOGUE = {
"mug": 9.99,
"tote": 14.50,
"pencil": 1.25,
}
DISCOUNTS = {"SAVE10": 0.10, "SAVE20": 0.20}
def unit_price(item: str) -> float:
"""Return the unit price of an item."""
return CATALOGUE[item]
def apply_discount(amount: float, code: str | None = None) -> float:
"""Return the amount with any discount applied, plus tax."""
rate = DISCOUNTS.get(code or "", 0.0)
return (amount * (1 - rate)) * (1 + TAX_RATE)
if __name__ == "__main__":
print("mug price:", unit_price("mug"))
print("20.00 with SAVE10:", round(apply_discount(20.00, "SAVE10"), 2))
# shop/cart.py
"""Basket operations built on the pricing rules."""
from .pricing import apply_discount, unit_price
def add_item(basket: dict[str, int], item: str, qty: int = 1) -> dict[str, int]:
"""Add qty of item to the basket and return the same basket."""
if qty <= 0:
raise ValueError("quantity must be positive")
basket[item] = basket.get(item, 0) + qty
return basket
def subtotal(basket: dict[str, int]) -> float:
"""Return the pre-tax, pre-discount total."""
return sum(unit_price(item) * qty for item, qty in basket.items())
def cart_total(basket: dict[str, int], code: str | None = None) -> float:
"""Return the final total including discount and tax."""
return apply_discount(subtotal(basket), code)
if __name__ == "__main__":
demo: dict[str, int] = {}
add_item(demo, "tote")
print("subtotal:", round(subtotal(demo), 2))
# shop/__init__.py
"""A tiny shopping-basket package used to show package layout."""
from .cart import add_item, cart_total, subtotal
from .pricing import CATALOGUE, TAX_RATE, apply_discount
__all__ = [
"add_item",
"cart_total",
"subtotal",
"apply_discount",
"CATALOGUE",
"TAX_RATE",
]
__version__ = "1.0.0"
# shop/__main__.py
"""Run the package demo with: python -m shop"""
from .cart import add_item, cart_total
def main() -> None:
basket: dict[str, int] = {}
add_item(basket, "mug", 2)
add_item(basket, "pencil")
print("basket:", basket)
print(f"total: {cart_total(basket):.2f}")
print(f"total with SAVE10: {cart_total(basket, 'SAVE10'):.2f}")
if __name__ == "__main__":
main()
Every module carries its own if __name__ == "__main__": demo, so you can run any of them directly with python -m shop.cart while you work. That habit - a runnable module - is what keeps a package debuggable as it grows.
Should you use relative or absolute imports?
Inside a package, the leading dot means "relative to the package I live in". One dot is the current package, two dots is the parent package. The cart.py listing above uses from .pricing import apply_discount, unit_price, which means "the pricing module that sits next to me". The same module can also be named from the outside with from shop.pricing import unit_price.
import shop
from shop import add_item, cart_total
from shop.pricing import unit_price
print("version:", shop.__version__)
print("public names:", len(shop.__all__))
print("mug price:", unit_price("mug"))
basket = {}
add_item(basket, "mug", 2)
print("total:", round(cart_total(basket), 2))
| Style | Example | Use it when |
|---|---|---|
| Relative | from .pricing import unit_price | You are writing code inside a package and the package may be renamed or vendored. |
| Absolute | from shop.pricing import unit_price | You are writing a script, a test, or code that lives outside the package. |
| Implicit relative | from pricing import unit_price | Never in Python 3. It raises ModuleNotFoundError and used to work in Python 2. |
Mixing the two styles in the same file is the smell to watch for. Pick relative imports inside packages and absolute imports in tests and scripts, and the whole codebase stays predictable when you move or rename a package.
How do you actually run a package?
There are two supported entry points, and they behave differently.
# run the package's __main__.py from the folder that contains it
python -m shop
# run one module of the package
python -m shop.cart
# what does NOT work from outside the package folder
python shop/__main__.py
The -m flag is not sugar. When you run python -m shop, Python puts your current working directory on sys.path and imports shop as a package, so the relative imports inside it resolve. When you run python shop/__main__.py, Python treats that file as a top-level script, the package context is gone, and from .cart import add_item fails with ImportError: attempted relative import with no known parent package.
The same mechanism powers the console_scripts entry points that packaging tools generate. Install your package and the console script calls shop.__main__:main for you - which is exactly why a __main__.py is worth writing.
One wrinkle is worth knowing about, because it looks alarming the first time you meet it. Because shop/__init__.py imports cart eagerly, running python -m shop.cart makes Python 3.13 print RuntimeWarning: 'shop.cart' found in sys.modules after import of package 'shop'. Nothing is broken - the module was simply imported once as part of the package and once as the program you asked for. It is a good argument for keeping real entry points in __main__.py and treating per-module runs as a debugging convenience.
Which folder layout should you pick?
| Layout | Shape | Verdict |
|---|---|---|
| Flat | myapp/ and tests/ sit next to pyproject.toml at the root | Perfect for a small project or a learning exercise. The simplest thing that works. |
| Src | src/myapp/ holds the code, tests live outside it | The best default for anything you intend to publish. Tests import the installed package, not the folder, so a broken layout fails loudly instead of silently. |
| Namespace | No __init__.py anywhere | For splitting one package across several distributions. Rare, advanced, and worth avoiding until you need it. |
Whatever layout you choose, the import statements stay identical. That is the point: layout is a deployment decision, and the import path is a public contract.
With the src layout, the code lives in src/myapp/ and nothing sits loose at the root. That forces you to install the package before your tests can import it, which sounds like friction but is the point: it proves the package is importable from outside its own folder, exactly as a user would experience it. A flat layout lets import myapp succeed just because you happened to run pytest from the right directory, and that success evaporates the moment someone runs it from elsewhere.
Quick reference
__init__.pymarks a directory as a regular package and runs on every import of that package.- Re-export deliberately with explicit imports plus
__all__; keep the file free of heavy work. - Use
from .module import nameinside a package, absolute imports in scripts and tests. - Give the package a
__main__.pysopython -m packageworks and console scripts have a target. - Choose the src layout for anything you plan to publish; the flat layout is fine for learning.
Try it yourself: Build the four-file shop package from this page in a folder called shop-lab. Run python -m shop from inside shop-lab and note the three output lines. Then run python shop/__main__.py from the same folder and read the error - it shows exactly why -m exists. Finally, add a fifth module shop/loyalty.py with a points_for(total) function, import it from shop/__init__.py, and confirm python -m shop still works.