Skip to content

41. Packages

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.py

You can import it as:

import app
import app.config
from app.server import run

The 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'>
email

A 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 lookup

41.2 The Role of __init__.py

A regular package usually has an __init__.py file.

project/
    app/
        __init__.py
        config.py
        routes.py

When CPython imports app, it executes:

app/__init__.py

The 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.py

and this code:

import demo

CPython 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 namespace

After import:

import sys
import demo

print(sys.modules["demo"] is demo)

Output:

True

The package is cached in sys.modules under its fully qualified name.

41.4 Importing Submodules

For:

import demo.util

CPython imports the parent package first.

Conceptually:

import demo
then search demo.__path__ for util
then import demo.util
then set demo.util attribute

After a successful import:

import demo.util

print(demo.util)
print(demo.util.__name__)

Output shape:

<module 'demo.util' from '.../demo/util.py'>
demo.util

The 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.http

Each 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.http

The 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.config

it searches app.__path__, not the full top-level sys.path.

This distinction is central:

ImportSearch path
import appsys.path
import app.configapp.__path__
import app.server.httpapp.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_locations

For 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:

AttributeMeaning
__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 Settings

The leading dot means:

resolve "config" relative to the current package

For the module app.server, the package context is usually:

app

So:

from .config import Settings

resolves to:

from app.config import Settings

For 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.py

and this import:

import app.server

CPython executes in this order:

1. app/__init__.py
2. app/server.py

If server.py imports config.py:

# app/server.py
from . import config

then execution becomes:

1. app/__init__.py
2. app/server.py starts
3. app/config.py executes
4. app/server.py continues

Parent packages are loaded before child modules.

41.11 Package Attributes for Submodules

After:

import app.server

the parent package usually receives an attribute:

app.server

This 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.config

binds app in the caller namespace.

from app import config

binds 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:

True

The 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.py

Internal 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, HTTPKitError

This 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 Tracker

But now:

import package

loads 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 Client

Good 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.Client

imports .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.py

Properties:

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 subpackages

Most 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.py

If both dir1 and dir2 are on sys.path, then plugins can be a namespace package.

import plugins.alpha
import plugins.beta

The 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

FeatureRegular packageNamespace package
Has __init__.pyYesNo
Executes package initialization codeYesNo single initializer
Can define package-level names directlyYesLimited
Can span multiple directoriesUsually noYes
Common useNormal libraries and appsPlugin 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.py

You can import:

import app.api
import app.api.users

The import system resolves each level.

app
app.api
app.api.users

Each 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 errors

Dots mean levels:

SyntaxMeaning
from . import xImport sibling from current package
from .x import yImport from child module in current package
from .. import xImport from parent package
from ..x import yImport 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.py

Inside main.py:

from .config import Settings

This works:

python -m app.main

This may fail:

python app/main.py

When 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.main

41.23 __main__.py

A package can define __main__.py.

tool/
    __init__.py
    __main__.py
    cli.py

Then:

python -m tool

executes:

tool/__main__.py

Example:

# 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 package

performs that work immediately.

This can be problematic for:

startup time
tests
CLI responsiveness
server cold starts
configuration ordering
optional dependencies
import cycles

Keep package initialization small when possible.

Good __init__.py files usually contain:

version constants
cheap re-exports
small compatibility shims
public API declarations

Avoid 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.py

Good dependency direction:

main imports web
web imports domain
web imports storage
storage imports domain
domain imports no app-specific infrastructure

Poor dependency direction:

domain imports web
storage imports main
config imports route handlers
__init__.py imports everything

Bad 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.py

Use 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 Post

The 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 Config

Then inside server.py:

from app import Config

This forces app.__init__ to finish while it is still importing server.

A safer internal import is:

from .config import Config

Inside 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.py

A leading underscore means the module is internal by convention.

from package._internal import helper

This 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.py

This 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.json

Do 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 packages

For 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.py

Public API:

from library import Client, LibraryError

The 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 runtime

Improvement 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.name

This 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 broken

raises 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.py

One import path:

import app.state

Another accidental path:

import state

Now 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 caches

This 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:
    pass

If loaded as both:

import app.models
import models

then:

app.models.User is models.User

may be:

False

An 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:function

Conceptually, running the command imports the module and calls the function.

Example target:

# tool/cli.py

def main():
    ...

Entry point:

tool = tool.cli:main

This 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 discovery

A 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: bs4

The import system knows import names. Packaging tools know distribution names.

This distinction matters when reading dependency lists or package metadata.

import bs4

does 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__.py

For 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 module

This model captures the package-specific parts:

load parent first
search parent.__path__
cache child by fully qualified name
bind child as parent attribute

41.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

SymptomLikely cause
Relative import failedModule run as a script instead of with -m
Partially initialized packageCircular import
Missing package attributeSubmodule has not been imported or facade does not export it
Two singleton instancesSame module imported under two names
Slow import packageHeavy __init__.py or transitive imports
Works in repo but fails after installBad package layout or missing package data
Standard module shadowedLocal 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.