Apps Artificial Intelligence CSS DevOps Go JavaScript Laravel Linux MongoDB MySQL PHP Python Rust Svelte Vue

Understanding `__init__.py` in Python

1 min read .
Understanding `__init__.py` in Python

The __init__.py file is commonly used to define a regular Python package and control what happens when that package is imported. It can be empty, expose a package-level API, define metadata, or run lightweight initialization code.

1. A Basic Package

Consider this structure:

my_package/
├── __init__.py
├── module1.py
└── module2.py

With __init__.py present, my_package is a regular package and its modules can be imported normally:

from my_package import module1
from my_package.module2 import some_function

Since Python 3.3, namespace packages can exist without __init__.py, so the file is no longer required for every possible package layout. It is still the standard choice for ordinary application and library packages.

2. Re-export a Public API

Suppose module1.py and module2.py contain useful functions. You can expose them at the package level:

# my_package/__init__.py
from .module1 import some_function
from .module2 import another_function

Callers can then write:

from my_package import some_function, another_function

This can provide a simpler and more stable public API even if the internal module structure changes later.

3. What __all__ Does

You may define:

from .module1 import some_function
from .module2 import another_function

__all__ = ["some_function", "another_function"]

__all__ primarily controls which names are imported by:

from my_package import *

It can also communicate the intended public API to readers and tooling, but it does not make other attributes truly private.

4. Package Metadata

Small constants can live in __init__.py:

__version__ = "1.0.0"

For installed package versions, modern projects may instead read metadata through importlib.metadata so the version has a single source of truth.

5. Avoid Heavy Import-Time Work

Code in __init__.py runs when the package is imported. Avoid slow network requests, large database connections, expensive filesystem scans, or other surprising side effects there.

Prefer lightweight imports and definitions:

from .client import Client

__all__ = ["Client"]

6. Relative Imports

Inside a package, a leading dot refers to the current package:

from .module1 import some_function

Two dots refer to the parent package:

from ..shared import helper

Use relative imports deliberately; absolute imports are often easier to understand across larger codebases.

Conclusion

__init__.py remains an important part of conventional Python packages. Keep it lightweight, use it to expose a clear package-level API when helpful, and remember that namespace packages are a separate mechanism that can work without the file.

Related Posts

chevron-up