Package __init__.py, namespace packages (PEP 420), relative imports, and __path__ manipulation.
A package is a module that can contain other modules. In CPython, a package is not a separate object category. It is still a module object, but it has import metadata that tells the import system where to look for submodules.
At the Python level, this directory can be a package:
app/
__init__.py
config.py
server.pyYou can import it as:
import app
import app.config
from app.server import runThe important rule is simple:
A package is a module with submodule search locations.For a normal file module, the import system loads one file. For a package, the import system also records where child modules may be found.
41.1 Packages Are Modules
A package object has type module.
import email
print(type(email))
print(email.__name__)Output:
<class 'module'>
emailA package has a module dictionary just like any other module.
import email
print(email.__dict__)The package namespace stores normal names:
__name__
__doc__
__package__
__loader__
__spec__
__path__
__file__
__cached__It can also store functions, classes, constants, imported submodules, and re-exported public API names.
A package is therefore both:
a namespace object
a container for submodule lookup41.2 The Role of __init__.py
A regular package usually has an __init__.py file.
project/
app/
__init__.py
config.py
routes.pyWhen CPython imports app, it executes:
app/__init__.pyThe code in __init__.py initializes the package namespace.
Example:
# app/__init__.py
VERSION = "1.0.0"
def create_app():
return "app"Then:
import app
print(app.VERSION)
print(app.create_app())The file __init__.py is not just a marker. It is executable module code.
41.3 Minimal Package Import
For this layout:
demo/
__init__.py
util.pyand this code:
import demoCPython roughly performs:
find package named "demo"
create module object for "demo"
set package metadata
insert "demo" into sys.modules
execute demo/__init__.py
bind name "demo" in caller namespaceAfter import:
import sys
import demo
print(sys.modules["demo"] is demo)Output:
TrueThe package is cached in sys.modules under its fully qualified name.
41.4 Importing Submodules
For:
import demo.utilCPython imports the parent package first.
Conceptually:
import demo
then search demo.__path__ for util
then import demo.util
then set demo.util attributeAfter a successful import:
import demo.util
print(demo.util)
print(demo.util.__name__)Output shape:
<module 'demo.util' from '.../demo/util.py'>
demo.utilThe submodule is cached separately:
import sys
import demo
import demo.util
print(sys.modules["demo"])
print(sys.modules["demo.util"])
print(demo.util is sys.modules["demo.util"])A parent package and a submodule are different module objects.
41.5 Fully Qualified Module Names
Packages create hierarchical module names.
app
app.config
app.server
app.server.httpEach imported module has a fully qualified name.
import app.server.http
print(app.__name__)
print(app.server.__name__)
print(app.server.http.__name__)Output:
app
app.server
app.server.httpThe fully qualified name is the key used in sys.modules.
import sys
print(sys.modules["app"])
print(sys.modules["app.server"])
print(sys.modules["app.server.http"])This name-based identity is important. Importing the same file under two different names can create two independent module objects.
41.6 Package Search Locations
A package has __path__.
import app
print(app.__path__)For a regular package, __path__ usually contains the package directory.
['/path/to/project/app']When CPython imports:
import app.configit searches app.__path__, not the full top-level sys.path.
This distinction is central:
| Import | Search path |
|---|---|
import app | sys.path |
import app.config | app.__path__ |
import app.server.http | app.server.__path__ |
A package controls where its children are found.
41.7 __spec__ and Packages
Every modern imported module has a __spec__.
For packages, the module spec includes submodule search locations.
import app
print(app.__spec__)
print(app.__spec__.name)
print(app.__spec__.origin)
print(app.__spec__.submodule_search_locations)For a package, this is usually non-null:
app.__spec__.submodule_search_locationsFor an ordinary module, it is usually None.
The import system uses this to distinguish modules from packages.
41.8 Package Metadata
A regular package commonly has these attributes:
| Attribute | Meaning |
|---|---|
__name__ | Fully qualified package name |
__package__ | Package context used for relative imports |
__path__ | Locations searched for submodules |
__spec__ | Import specification |
__loader__ | Loader that initialized the package |
__file__ | Path to __init__.py, if file-backed |
__cached__ | Path to cached bytecode, if available |
Example:
import json
print(json.__name__)
print(json.__package__)
print(json.__path__)
print(json.__file__)
print(json.__cached__)A package is therefore observable as a normal object.
41.9 __package__
The __package__ attribute controls relative import resolution.
Inside app/server.py:
from .config import SettingsThe leading dot means:
resolve "config" relative to the current packageFor the module app.server, the package context is usually:
appSo:
from .config import Settingsresolves to:
from app.config import SettingsFor a package module such as app, __package__ is usually "app".
For a submodule such as app.server, __package__ is usually "app".
For a nested submodule such as app.http.server, __package__ is usually "app.http".
41.10 Package Execution Order
Given this layout:
app/
__init__.py
config.py
server.pyand this import:
import app.serverCPython executes in this order:
1. app/__init__.py
2. app/server.pyIf server.py imports config.py:
# app/server.py
from . import configthen execution becomes:
1. app/__init__.py
2. app/server.py starts
3. app/config.py executes
4. app/server.py continuesParent packages are loaded before child modules.
41.11 Package Attributes for Submodules
After:
import app.serverthe parent package usually receives an attribute:
app.serverThis attribute points to the submodule object.
Equivalent view:
import sys
import app.server
assert app.server is sys.modules["app.server"]This binding matters because user code often navigates through package attributes:
import app.server
app.server.run()The import system maintains the connection between the parent package and the child module.
41.12 import package.module vs from package import module
These two forms are similar but not identical in name binding.
import app.configbinds app in the caller namespace.
from app import configbinds config in the caller namespace.
Both normally load app.config.
Example:
import app.config
print(app.config)from app import config
print(config)The loaded module object is usually the same:
import app.config
from app import config
print(app.config is config)Output:
TrueThe difference is the name placed in the importing module’s namespace.
41.13 Public Package API
A package can expose a clean public API through __init__.py.
Example layout:
httpkit/
__init__.py
client.py
response.py
errors.pyInternal files:
# httpkit/client.py
class Client:
...# httpkit/errors.py
class HTTPKitError(Exception):
...Facade:
# httpkit/__init__.py
from .client import Client
from .errors import HTTPKitError
__all__ = ["Client", "HTTPKitError"]Users can write:
from httpkit import Client, HTTPKitErrorThis lets the package author change internal file layout while preserving public imports.
41.14 __all__
The name __all__ defines the public names used by star import.
__all__ = ["Client", "HTTPKitError"]For:
from httpkit import *Python imports names listed in httpkit.__all__.
Without __all__, star import exports names that do not begin with _.
__all__ is also useful as documentation. It tells readers which names are intended as public package API.
41.15 Package Facades and Import Cost
A package facade improves ergonomics but can increase import time.
This is convenient:
# package/__init__.py
from .database import Database
from .server import Server
from .client import Client
from .analytics import TrackerBut now:
import packageloads all those modules.
That may be expensive if those modules import large dependencies, initialize native libraries, read files, or perform configuration.
A lighter package facade may expose only cheap names:
# package/__init__.py
__version__ = "1.0.0"Then users import heavy components directly:
from package.client import ClientGood package design balances convenience, startup time, and dependency clarity.
41.16 Lazy Package Attributes
A package can lazily expose attributes using module-level __getattr__.
# package/__init__.py
def __getattr__(name):
if name == "Client":
from .client import Client
return Client
raise AttributeError(name)Then:
import package
Client = package.Clientimports .client only when Client is requested.
This can reduce startup cost while preserving a nice public API.
The tradeoff is complexity. Lazy package exports make import behavior less obvious, and errors may appear later.
41.17 Regular Packages
A regular package has an __init__.py.
pkg/
__init__.py
mod.pyProperties:
executes __init__.py when imported
has __file__ pointing to __init__.py
has __path__ pointing to package directory
can define package-level API
can contain submodules and subpackagesMost application packages and libraries use regular packages.
41.18 Namespace Packages
A namespace package has no single __init__.py.
It can be assembled from multiple directories.
Example:
dir1/
plugins/
alpha.py
dir2/
plugins/
beta.pyIf both dir1 and dir2 are on sys.path, then plugins can be a namespace package.
import plugins.alpha
import plugins.betaThe package plugins may have a __path__ containing both locations.
Namespace packages are useful when multiple distributions contribute to one package namespace.
41.19 Regular Package vs Namespace Package
| Feature | Regular package | Namespace package |
|---|---|---|
Has __init__.py | Yes | No |
| Executes package initialization code | Yes | No single initializer |
| Can define package-level names directly | Yes | Limited |
| Can span multiple directories | Usually no | Yes |
| Common use | Normal libraries and apps | Plugin namespaces, split distributions |
A regular package gives explicit initialization. A namespace package gives flexible composition.
41.20 Subpackages
A subpackage is a package inside another package.
app/
__init__.py
api/
__init__.py
users.py
posts.pyYou can import:
import app.api
import app.api.usersThe import system resolves each level.
app
app.api
app.api.usersEach level has its own module object and its own sys.modules entry.
import sys
import app.api.users
print(sys.modules["app"])
print(sys.modules["app.api"])
print(sys.modules["app.api.users"])41.21 Relative Imports in Packages
Relative imports are common inside packages.
from .config import Settings
from .storage import Store
from ..core import errorsDots mean levels:
| Syntax | Meaning |
|---|---|
from . import x | Import sibling from current package |
from .x import y | Import from child module in current package |
from .. import x | Import from parent package |
from ..x import y | Import from sibling under parent package |
Relative imports make internal dependencies independent of the top-level package name.
They also require correct package context. Running a package file directly can break them.
41.22 Direct Script Execution Problem
Given:
app/
__init__.py
main.py
config.pyInside main.py:
from .config import SettingsThis works:
python -m app.mainThis may fail:
python app/main.pyWhen executed by file path, main.py becomes __main__, not app.main. The module may lose the package context needed for relative imports.
Use module execution for package code:
python -m app.main41.23 __main__.py
A package can define __main__.py.
tool/
__init__.py
__main__.py
cli.pyThen:
python -m toolexecutes:
tool/__main__.pyExample:
# tool/__main__.py
from .cli import main
main()This is the standard way to make a package executable.
41.24 Package Initialization Side Effects
Because __init__.py executes during import, package initialization can have side effects.
# package/__init__.py
print("loading package")
connect_to_service()Then:
import packageperforms that work immediately.
This can be problematic for:
startup time
tests
CLI responsiveness
server cold starts
configuration ordering
optional dependencies
import cyclesKeep package initialization small when possible.
Good __init__.py files usually contain:
version constants
cheap re-exports
small compatibility shims
public API declarationsAvoid heavy work unless import-time initialization is part of the explicit package contract.
41.25 Package Dependency Graphs
Package structure should reflect dependency direction.
A clean application might look like:
app/
__init__.py
main.py
config.py
domain/
__init__.py
users.py
posts.py
storage/
__init__.py
db.py
web/
__init__.py
routes.pyGood dependency direction:
main imports web
web imports domain
web imports storage
storage imports domain
domain imports no app-specific infrastructurePoor dependency direction:
domain imports web
storage imports main
config imports route handlers
__init__.py imports everythingBad package dependency graphs often produce circular imports.
41.26 Circular Imports in Packages
Circular imports are common in packages because modules often import siblings.
Example:
# app/users.py
from .posts import Post
class User:
...# app/posts.py
from .users import User
class Post:
...This may fail because app.users and app.posts need each other during top-level execution.
Possible fixes:
Move shared definitions:
app/
models.py
users.py
posts.pyUse local imports:
def create_post():
from .posts import Post
return Post()Use type-checking-only imports:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .posts import PostThe structural fix is usually best. Circular imports often indicate that module boundaries need adjustment.
41.27 Package-Level Re-Exports and Circular Imports
Re-exporting too much in __init__.py can create import cycles.
Example:
# app/__init__.py
from .server import Server
from .config import ConfigThen inside server.py:
from app import ConfigThis forces app.__init__ to finish while it is still importing server.
A safer internal import is:
from .config import ConfigInside a package, prefer importing from the defining module rather than from the package facade.
The package facade is mostly for external users.
41.28 Private Modules
Python uses naming conventions for private modules.
package/
__init__.py
public.py
_internal.py
_compat.pyA leading underscore means the module is internal by convention.
from package._internal import helperThis is allowed, but package authors may change _internal without preserving compatibility.
Public API should be documented and re-exported deliberately.
41.29 src Layout
Many Python projects use a src layout.
project/
pyproject.toml
src/
package/
__init__.py
core.py
tests/
test_core.pyThis layout helps prevent accidental imports from the repository root.
Without src, tests may import local files by accident even when the installed package is broken.
With src, the package must be installed or the path must be configured correctly, which better matches user behavior.
41.30 Package Data
Packages can contain data files.
package/
__init__.py
templates/
page.html
data/
defaults.jsonDo not assume package data lives in an ordinary file-system directory. Packages may be imported from zip files or other loaders.
Prefer importlib.resources:
from importlib.resources import files
data = files("package.data").joinpath("defaults.json").read_text()This asks the import system for the resource instead of manually building paths from __file__.
41.31 __file__ Limitations
Many packages have __file__.
import package
print(package.__file__)But robust code should not assume all modules and packages have normal file paths.
Some modules may be:
built in
frozen
loaded from zip files
loaded by custom importers
namespace packagesFor package resources, use import-system APIs rather than direct path arithmetic when possible.
41.32 Package Version Values
Packages often define __version__.
# package/__init__.py
__version__ = "1.2.3"This is simple and common.
A package may also use installed package metadata:
from importlib.metadata import version
__version__ = version("package-name")The second approach avoids duplicating version strings, but it can fail if the package is not installed as metadata.
For libraries, keep version handling simple and predictable.
41.33 Public API Stability
Package layout and public API are different things.
Internal layout:
library/
_client.py
_transport.py
_errors.pyPublic API:
from library import Client, LibraryErrorThe package can preserve the public API while changing internals.
# library/__init__.py
from ._client import Client
from ._errors import LibraryError
__all__ = ["Client", "LibraryError"]This separation gives maintainers freedom to refactor.
41.34 Package Import Time
Package import time can be measured.
python -X importtime -c "import package"Slow package imports often come from:
large transitive imports
heavy package __init__.py files
runtime configuration loading
native library initialization
network or file-system work
plugin auto-discovery
large type-hint imports at runtimeImprovement usually starts with making __init__.py smaller.
41.35 Package Initialization Pattern
A practical __init__.py often looks like:
"""
Public API for examplekit.
"""
from .client import Client
from .errors import ExampleKitError
__all__ = [
"Client",
"ExampleKitError",
]
__version__ = "0.1.0"This is acceptable when client and errors are cheap to import.
For heavier modules:
__all__ = ["Client", "ExampleKitError", "__version__"]
__version__ = "0.1.0"
def __getattr__(name):
if name == "Client":
from .client import Client
return Client
if name == "ExampleKitError":
from .errors import ExampleKitError
return ExampleKitError
raise AttributeError(name)Use the lazy form only when startup cost justifies the complexity.
41.36 Packages and CPython Internals
At the CPython level, package import is still module import.
The difference appears in import metadata:
regular module:
__spec__.submodule_search_locations = None
package:
__spec__.submodule_search_locations = [...]
__path__ = [...]When importing a child module, the import system uses the parent package path.
Simplified:
def import_child(parent, child_name):
fullname = parent.__name__ + "." + child_name
path = parent.__path__
spec = find_spec(fullname, path)
return load(spec)The real import system handles locking, errors, namespace packages, caches, and loader protocols.
41.37 Package Objects and Attribute Lookup
A package object uses normal module attribute lookup.
import package
package.nameThis looks in the package dictionary.
If a package defines module-level __getattr__, missing attributes can be computed dynamically.
def __getattr__(name):
...But imported submodules are usually stored as attributes on the parent package.
import package.submodule
print(package.submodule)This is why package namespaces can grow as imports happen.
41.38 Failed Package Imports
If package import fails during __init__.py, the import system removes the failing module from sys.modules in many cases.
Example:
# broken/__init__.py
raise RuntimeError("failed")Then:
import brokenraises an exception.
The import system must avoid leaving a broken module cached as if it were successfully initialized.
Submodule failures can be subtler. A parent package may import successfully while a child module fails.
41.39 Duplicate Package Identity
Duplicate package identity happens when the same package is importable under different names.
Example path problem:
project/
app/
__init__.py
state.pyOne import path:
import app.stateAnother accidental path:
import stateNow the same file may be loaded twice:
sys.modules["app.state"]
sys.modules["state"]Consequences:
two module global dictionaries
two singleton instances
two registry objects
two class identities
two cachesThis is a common source of strange bugs.
Avoid it by using consistent absolute package imports and avoiding unsafe sys.path edits.
41.40 Packages and Type Identity
Classes are objects stored in modules.
If the same module is imported twice under different names, its classes are created twice.
# app/models.py
class User:
passIf loaded as both:
import app.models
import modelsthen:
app.models.User is models.Usermay be:
FalseAn object created from one class may fail isinstance checks against the other.
This is why module identity matters for packages.
41.41 Packages and Entry Points
Installed packages can expose console scripts through packaging metadata.
A command-line entry point may call:
package.module:functionConceptually, running the command imports the module and calls the function.
Example target:
# tool/cli.py
def main():
...Entry point:
tool = tool.cli:mainThis means CLI startup cost includes importing tool.cli and its dependencies.
Keep CLI entry modules small when startup matters.
41.42 Plugin Packages
Packages often support plugins.
A plugin architecture may use:
namespace packages
entry points
importlib
explicit plugin lists
dynamic discoveryA simple explicit plugin loader:
import importlib
def load_plugin(name):
return importlib.import_module(f"app_plugins.{name}")A package-based plugin system should avoid importing every plugin eagerly unless needed.
Plugin imports often have side effects, such as registration.
# plugin_alpha.py
from registry import register
register("alpha", handler)This is useful, but import order becomes part of program behavior.
41.43 Packages and Distribution Names
Import package names and distribution package names can differ.
Example:
distribution name: beautifulsoup4
import name: bs4The import system knows import names. Packaging tools know distribution names.
This distinction matters when reading dependency lists or package metadata.
import bs4does not say the installed distribution was named bs4.
41.44 Packages and pyproject.toml
Modern Python packages commonly use pyproject.toml.
A project may declare:
[project]
name = "examplekit"
version = "0.1.0"The distribution name is examplekit.
The import package may be:
src/examplekit/
__init__.pyFor CPython’s import system, only the importable package layout and sys.path matter at runtime. Build metadata matters before runtime, during installation and packaging.
41.45 Editable Installs
In development, packages are often installed in editable mode.
python -m pip install -e .This makes the package importable from the working tree.
From CPython’s perspective, the result is still path-based import. The environment has been configured so that the package source location appears in import resolution.
Editable installs help tests and tools import the package as users would, while still using live source files.
41.46 Package Debugging Checklist
When a package import behaves strangely, inspect:
import sys
import package
print(package)
print(package.__name__)
print(getattr(package, "__file__", None))
print(getattr(package, "__path__", None))
print(package.__spec__)
print(sys.modules.get("package"))For a submodule:
import package.submodule
print(package.submodule)
print(package.submodule.__name__)
print(package.submodule.__file__)
print(sys.modules.get("package.submodule"))For path issues:
import sys
for p in sys.path:
print(p)For resolution without importing:
import importlib.util
print(importlib.util.find_spec("package"))
print(importlib.util.find_spec("package.submodule"))41.47 Minimal Package Import Model
A simplified model for importing a submodule:
def import_package_child(parent_name, child_name):
parent = import_module(parent_name)
fullname = parent_name + "." + child_name
if fullname in sys.modules:
return sys.modules[fullname]
spec = find_spec(fullname, parent.__path__)
if spec is None:
raise ModuleNotFoundError(fullname)
module = module_from_spec(spec)
sys.modules[fullname] = module
try:
spec.loader.exec_module(module)
except Exception:
del sys.modules[fullname]
raise
setattr(parent, child_name, module)
return moduleThis model captures the package-specific parts:
load parent first
search parent.__path__
cache child by fully qualified name
bind child as parent attribute41.48 Good Package Design Rules
Keep package initialization small.
Use __init__.py to expose stable public API, not to run the application.
Prefer direct internal imports from defining modules.
Avoid having internal modules import from the package facade.
Avoid direct script execution for package modules. Use python -m package.module.
Use importlib.resources for package data.
Use __all__ to document public names.
Use namespace packages only when split package composition is needed.
Avoid modifying sys.path inside package code.
Keep dependency direction clear.
41.49 Common Package Failures
| Symptom | Likely cause |
|---|---|
| Relative import failed | Module run as a script instead of with -m |
| Partially initialized package | Circular import |
| Missing package attribute | Submodule has not been imported or facade does not export it |
| Two singleton instances | Same module imported under two names |
Slow import package | Heavy __init__.py or transitive imports |
| Works in repo but fails after install | Bad package layout or missing package data |
| Standard module shadowed | Local file or package name collision |
41.50 Key Points
A package is a module with submodule search locations.
A regular package executes __init__.py during import.
Submodules are cached in sys.modules under fully qualified names.
A package’s __path__ controls where child modules are searched.
Relative imports depend on package context.
Namespace packages allow one package namespace to span multiple locations.
Package facades improve API ergonomics but can increase import cost and create cycles.
Most package bugs come from circular imports, path mistakes, duplicate module identity, heavy initialization, or running package files directly.