A package is a directory of modules grouped under a single name, and the file at its center is __init__.py. It marks a directory as a package, runs once when the package is first imported, and acts as the package's namespace. Every shaping decision about how a package looks to callers happens in __init__.py. This lesson covers what the file does, what to put inside it, the common patterns (thin re-exports, lazy imports, package-level constants), and the modern alternative (namespace packages) for the rare cases where skipping __init__.py makes sense.
__init__.py Does__init__.py is a file that lives at the root of a package directory. Its presence tells Python "this directory is a regular package, not a folder of unrelated scripts." Without it (in older Python or in projects that don't opt into namespace packages), the directory is a folder and the dotted imports won't work.
The file itself is a regular Python module. Anything that goes in a normal .py file can go here. The difference is that this particular file represents the package itself. When code does import ecommerce, Python runs ecommerce/__init__.py and the resulting module object is the ecommerce package. The package's namespace is whatever names exist after __init__.py finishes running.
The simplest case is a directory:
ecommerce/cart.py:
ecommerce/products.py:
ecommerce/__init__.py is completely empty.
From a script next to the ecommerce/ directory:
The empty __init__.py did one thing: it made Python treat ecommerce/ as a regular package, which is what makes from ecommerce.cart import ... resolve. No code ran (there was none to run), no names got bound on the package, but the dotted imports work.
The next step up is a populated __init__.py. Change ecommerce/__init__.py to:
Now from a caller:
Three imports of ecommerce (one direct, two indirect through submodule imports), but __init__.py runs once. That's the same caching behavior every Python module has: the first import runs the body, every later import returns the cached module object from sys.modules. Earlier chapters covered this; it applies to __init__.py the same way it applies to any other module.
The PACKAGE_VERSION constant became an attribute on the ecommerce package, reachable as ecommerce.PACKAGE_VERSION. Package-level names are exposed by being defined at the top level of __init__.py, or by being imported into it from a submodule.
The diagram shows the lifecycle. The first import (whether import ecommerce directly or from ecommerce.cart import add_item indirectly) triggers running __init__.py. The body runs once, builds the package namespace, and the result lands in sys.modules under the key 'ecommerce'. Every later import (no matter the form) reuses that cached object.
__init__.pyThe first case to consider is the empty __init__.py. Zero bytes, no code, no docstring. That's a valid package, and for many simple cases it's enough.
What it accomplishes:
from package.submodule import name.__name__, __file__, __path__).When this is the right choice:
A small example. A project's ecommerce/ package has three submodules and the caller always uses the deep paths:
Each submodule loads only when something asks for it. The empty __init__.py introduces zero overhead. There's no extra code to maintain. A small project can keep the file empty for its entire life.
The empty version is also a sensible starting point for any package. Begin with an empty file and add to it only with a concrete reason: a flattened API, a package-level constant, an __all__ declaration for tooling. Premature __init__.py content tends to drift into noise; starting empty and adding deliberately keeps the file purposeful.
One thing to watch: in a Python 2 codebase or a very old Python 3 codebase, an empty __init__.py is the only way to mark a directory as a package. Namespace packages (no __init__.py at all) only became fully supported in Python 3.3 via PEP 420. For modern code on modern Python (3.3+), both options exist, and the empty-file approach is still preferred for self-contained projects.
The most common reason to put code in __init__.py is to re-export names from submodules so callers can reach them through the package's top-level name. This is the pattern that makes deeply-nested code look flat from the outside.
Consider this layout:
Without re-exports, callers have to know which submodule owns which name:
Two things wrong here. First, the caller has to remember that add_item lives in cart.py versus, say, cart_utils.py or actions.py. Second, if anything is renamed or moved internally (moving add_item from cart.py to a new cart/actions.py), every caller's import breaks.
Re-exports fix both. Update ecommerce/__init__.py to:
Now callers can write:
The names are now attached to the ecommerce package itself. The caller doesn't need to know that add_item lives in cart.py; that's an implementation detail. Splitting cart.py later into cart/actions.py and cart/totals.py requires updating ecommerce/__init__.py once to re-export from the new locations, and every caller keeps working with zero changes.
This pattern is everywhere in the standard library. from collections import Counter doesn't pull Counter from a file called collections.py. The collections package re-exports Counter from one of its internal modules. Same idea: a flat public API, regardless of how the code is laid out internally.
The diagram traces what happens. The caller's import triggers loading __init__.py. Inside __init__.py, the submodule imports run, which loads cart.py and products.py. After __init__.py finishes, add_item, cart_total, PRICES, and is_available are all attributes of the ecommerce package. The caller's request for from ecommerce import add_item resolves through that namespace.
The trade-off is import cost. Every name re-exported in __init__.py causes its source submodule to load on the first package import. If cart.py does heavy work at import time (loads a big file, compiles regexes, opens a connection), import ecommerce pays for that work even if the caller never uses add_item. For small packages, this is fine. For large packages with expensive submodules, an alternative is to re-export only the most common names and leave heavier ones reachable only through their deep paths.
Eager re-exports trade startup time for caller convenience. A package with 30 submodules that all get loaded eagerly through __init__.py re-exports has a noticeable startup cost. Profile the package's import time if needed; tools like python -X importtime your_script.py show which modules are slow to load.
A rule of thumb: re-export the names callers use frequently. Leave rarely-used or expensive submodules accessible only through their full path. This keeps the common case fast and the unusual case still possible.
Constants that are part of the package's public API often belong directly in __init__.py. Version numbers, default settings, package-wide configuration: things that aren't specific to any one submodule.
Callers reach these through the package:
The PACKAGE_VERSION convention is common; many Python projects expose it under that name (or __version__, which is the dunder variant). Both work; pick one per project. Tools that introspect a package's version number look at one or the other first:
Beyond version numbers, the kinds of constants that fit at the package level are configuration that doesn't depend on any particular submodule. If a value is conceptually owned by pricing.py, it lives in pricing.py. If it's owned by the package as a whole, it lives in __init__.py. Mixing these wrong creates confusion: a TAX_RATE constant in __init__.py makes readers wonder whether it overrides something in pricing.py, and updates require touching both files.
One small note: don't import package-level constants into submodules through the package. Doing from ecommerce import PACKAGE_VERSION inside ecommerce/cart.py creates a circular import (cart needs ecommerce, ecommerce needs cart). Submodules that need a constant should either define their own or read it from a separate config module the package as a whole owns.
__all__ in __init__.py__all__ plays the same role in a package's __init__.py as it does in a regular module: it controls which names from package import * exports.
Without __all__, from ecommerce import * pulls every name that doesn't start with an underscore from the package's namespace. That includes any submodules imported into __init__.py, which is often more than intended.
With __all__, the public API is explicit:
_internal_helper exists in ecommerce's namespace (it was imported into __init__.py), but it isn't in __all__, so the star import skips it. Same goes for any other name not listed.
Even when no one writes from ecommerce import *, defining __all__ is good hygiene. Linters and type checkers use it to identify the public API. Documentation generators use it to decide which names show up in the rendered docs. IDEs use it for autocomplete prioritization. The file is more useful with __all__ than without.
A common style is to keep __all__ at the end of __init__.py, after all the imports and constants. That way the file reads top-to-bottom: docstring, imports, constants, then __all__ summarizing the public surface.
A reader's eye lands on __all__ and immediately sees the public API.
Eager re-exports in __init__.py load every referenced submodule at package import time. For a small package this is fine. For a large package, especially one with expensive submodules (a machine learning model loader, a database connector, a heavy parser), eager loading bloats startup time even when the caller only needs a lightweight name.
Lazy imports are the standard solution. Instead of importing a submodule eagerly, import it the first time someone asks for one of its names. The mechanism uses the module-level __getattr__ function, added in Python 3.7 by PEP 562.
What happens at runtime:
The first two operations don't touch the heavy modules. The third triggers the lazy load, and from then on ecommerce.load_full_catalog is cached on the package object (Python caches attribute access by default), so the cost is paid only once.
This pattern is most useful for packages where many users only need a small subset of the functionality, and the heavy submodules would otherwise slow down everyone's imports. It's used by numpy, scipy, scikit-learn, and many other large libraries. For small packages, it's overkill: the complexity of __getattr__ isn't worth the savings when the eager version takes 50 milliseconds.
To check whether a package would benefit, profile its import time first:
This prints the cumulative import time for every module loaded during import ecommerce. The output shows which submodules are the slow ones, and lazy loading is worth considering for any that take more than a few tens of milliseconds.
A simpler form of lazy loading: don't re-export the heavy modules in __init__.py at all. Callers who need them use the deep path:
This works without __getattr__ and is easier to read. The cost is that the public API looks split: some names live at the package root, others live one level down. Both forms (__getattr__ and "don't re-export") are common in larger codebases; pick whichever fits the project.
__init__.pyA common source of pain in packages is __init__.py files that do too much. Some side effects to keep out:
File and network I/O at import time. Reading a JSON config, opening a database connection, fetching a URL: all of these run the first time anything imports the package, which is rarely the intent. The cost is paid by every program that touches the package, even ones that don't need the data. Worse, if the file doesn't exist or the network is down, the import fails entirely, and the package becomes unusable. Push I/O into functions that the caller invokes deliberately.
Logging configuration. Configuring the logging system at package import time (calling logging.basicConfig, setting handlers, setting log levels) interferes with whatever the caller's application has already set up. Libraries should attach handlers only when explicitly asked, never automatically.
Mutating global state. __init__.py runs before the user has any say in what their program looks like. Avoid touching global state inside __init__.py to inject into the caller's environment. A package that needs to register itself with something should expose a register() function and let the caller invoke it.
Heavy computation. Building large lookup tables, training a model, sorting a big list of constants: any of this slows imports down. If the result is cheap to compute, do it lazily on first use. If it's expensive, ship pre-built data files and load them on demand.
Imports that fail. A __init__.py that imports a third-party library that might not be installed makes the entire package unusable when that import fails. Either make the dependency required (and let pip install your-package ensure it's there) or wrap the import in a try/except that falls back to a clear error message when the feature is used:
This makes the failure deferred and explicit instead of breaking every import of ecommerce.
The simplest rule: __init__.py should be predictable. A reader of the file should be able to glance at it and see what runs at import time. Big side effects, conditional logic, and external dependencies are red flags.
Side effects at import time are some of the hardest bugs to debug because they happen before any calling code starts running. Symptoms include slow startup, failures during simple imports, and ImportError traces that point to lines deep inside someone else's package. The fix is usually to move the side effect out of __init__.py and into a function the caller invokes explicitly.
There are two broad styles for __init__.py, and most packages use one or the other depending on size and intent.
Thin init (re-exports only). The file is a curated list of from ... import ... statements plus an __all__. All the logic lives in submodules; the init file shapes what callers see. This is the dominant style for small-to-medium packages.
Pros: easy to read, easy to maintain, clear separation between API shape (init) and implementation (submodules). Cons: every re-export forces its submodule to load eagerly.
Fat init (logic lives here). Functions, classes, and constants are defined directly inside __init__.py, sometimes alongside imports from submodules. This appears in small one-file-grew-into-a-package projects.
Pros: short package, fewer files, easy to read at a glance. Cons: doesn't scale; once __init__.py is more than a couple hundred lines, the rationale for being a package (organization) starts working against the layout.
The practical guidance: start fat, refactor to thin as the package grows. A brand-new package with three functions and a constant doesn't need a separate core.py; put everything in __init__.py. As the package accumulates dozens of names, gradually move logic into named submodules and turn __init__.py into a thin re-export layer.
The wrong move is the opposite: a 2,000-line __init__.py that defines everything itself. That's a giant pricing.py wearing a package's clothing. An __init__.py doing real work should split the work into submodules and turn the init file into a curated API.
A useful smell test: can the __init__.py be read top to bottom in 30 seconds? If yes, the file is healthy. If no, it's grown beyond what a package's init file should hold.
__init__.pyEvery directory in a package tree gets its own __init__.py. The rules are the same at every level: an empty file is enough to mark the directory as a regular package; a populated file can re-export, define constants, and set __all__ for that subpackage.
ecommerce/cart/__init__.py:
ecommerce/products/__init__.py:
The top-level ecommerce/__init__.py can then re-export from the subpackages:
A caller now has multiple ways to import any given name:
All three resolve to the same function. The maintainer chooses which path is the "official" one (usually the shallowest); the deeper paths remain available as fallbacks and for advanced users.
A note on import order: when Python loads ecommerce/__init__.py and that init does from ecommerce.cart import add_item, Python triggers loading ecommerce/cart/__init__.py, which triggers loading ecommerce/cart/operations.py. The chain runs top-down through the tree. If any of those steps has slow code, the cost rolls up to the top-level import.
The diagram shows the load order for import ecommerce when every __init__.py re-exports eagerly. The top-level init triggers the subpackage inits, which trigger the submodules. By the time ecommerce is fully loaded, the whole tree is in memory.
This is fine for small to medium packages. For larger ones, an alternative is to skip re-exporting heavy subpackages and let callers reach them through the deep path, saving the cost for those who need it.
__init__.pyPEP 420 (Python 3.3+) added a second kind of package called a namespace package: a directory without __init__.py that Python still treats as importable. The motivation was distributed development: large projects sometimes want to split a single logical package across multiple physical directories or installed distributions, and a regular package can't do that because only one __init__.py can own the package name.
A minimal example:
If both plugins-core/ and plugins-extras/ are on sys.path, then import ecommerce.cart and import ecommerce.wishlist both work, even though the two ecommerce/ directories are physically separate. Python merges them into a single namespace because neither contains an __init__.py.
This is useful for plugin systems. Consider a base package myapp where third parties publish their own plugins as separate distributions on PyPI. With a namespace package layout, each plugin can drop its module into myapp.plugins.<thing> from its own installed distribution, and Python merges them at runtime. Users install the base package and any combination of plugins, and they all show up under the same dotted name.
For ordinary self-contained projects, namespace packages are usually the wrong choice. The pitfalls:
__init__.py's ability to set up state or re-export names. There's no place to put a docstring, a version constant, or an __all__ declaration. Tools that look for package.__version__ find nothing.sys.path share a top-level name, Python merges them. This can mask typos and produce hard-to-debug "where did this module come from" sessions.pyproject.toml for a namespace-package distribution requires extra configuration.__init__.py, the package can't define __all__, so from package import * falls back to defaults that may not match the intent.The rule of thumb: include `__init__.py` for everything, even when it's empty. Use namespace packages only with a specific reason, almost always involving plugin extensibility from third parties.
The diagram lays out the trade-off. Regular packages are simpler, more controllable, and the right choice for self-contained projects. Namespace packages provide cross-distribution extensibility at the cost of every package-level feature __init__.py provides.
The two styles can also be mixed within one project: a regular myapp/ (with __init__.py) and a namespace myapp/plugins/ (without one) that third parties extend. That's a common architecture for plugin-aware applications.
A complete example that combines everything in the lesson. The layout:
ecommerce/cart/operations.py:
ecommerce/cart/discounts.py:
ecommerce/cart/__init__.py:
ecommerce/products/catalog.py:
ecommerce/products/__init__.py:
ecommerce/customers/profiles.py:
ecommerce/customers/__init__.py:
ecommerce/__init__.py:
A caller script next to the ecommerce/ directory:
The caller never had to know which submodule any name lived in. The top-level ecommerce/__init__.py re-exports from the subpackage inits, which re-export from their own submodules. Three layers of init files form a clean public API over a deeper internal layout.
If a maintainer later moves apply_percentage from cart/discounts.py to a new cart/promotions.py, only ecommerce/cart/__init__.py needs an update to re-export from the new location. The top-level __init__.py doesn't change because it already imports from ecommerce.cart, not from the deeper file. The caller doesn't change at all because it imports from ecommerce, which still exposes apply_percentage. That's the practical payoff of curating a package API through __init__.py.
10 quizzes