Python
__init__.py
package development
programming best practices
Python packaging

How do I write good/correct package __init__.py files

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

An __init__.py file marks a directory as a Python package and controls what gets imported when someone uses from mypackage import .... A good __init__.py does three things: defines __all__ to control from package import *, re-exports the public API for convenient access, and stays minimal (no heavy computation). In Python 3.3+, __init__.py is optional for namespace packages, but explicit packages with __init__.py are still the standard for most projects.

Minimal init.py

The simplest valid __init__.py is an empty file:

python
# mypackage/__init__.py
# Empty — just marks the directory as a package
 
1mypackage/
2    __init__.py
3    module_a.py
4    module_b.py
python
# Users import submodules explicitly
from mypackage.module_a import func_a
from mypackage.module_b import ClassB

Re-Exporting the Public API

A well-designed __init__.py re-exports key symbols so users do not need to know the internal module structure:

python
1# mypackage/__init__.py
2from mypackage.module_a import func_a, func_b
3from mypackage.module_b import ClassB, ClassC
4from mypackage.config import DEFAULT_TIMEOUT
5
6__all__ = ["func_a", "func_b", "ClassB", "ClassC", "DEFAULT_TIMEOUT"]
python
1# Users get a clean API
2from mypackage import func_a, ClassB
3
4# Instead of needing to know internal structure
5from mypackage.module_a import func_a
6from mypackage.module_b import ClassB

Defining all

__all__ controls what from package import * exports:

python
1# mypackage/__init__.py
2from mypackage.models import User, Product
3from mypackage.utils import format_date
4from mypackage._internal import _helper  # Private helper
5
6__all__ = ["User", "Product", "format_date"]
7# _helper is NOT in __all__ — not exported by import *
python
from mypackage import *
# Imports User, Product, format_date
# Does NOT import _helper

Without __all__, from package import * exports everything that does not start with _.

Package with Subpackages

 
1mypackage/
2    __init__.py
3    models/
4        __init__.py
5        user.py
6        product.py
7    utils/
8        __init__.py
9        formatting.py
10        validation.py
python
1# mypackage/models/__init__.py
2from mypackage.models.user import User
3from mypackage.models.product import Product
4
5__all__ = ["User", "Product"]
python
1# mypackage/__init__.py
2from mypackage.models import User, Product
3from mypackage.utils.formatting import format_date
4
5__all__ = ["User", "Product", "format_date"]
python
# Clean user-facing API
from mypackage import User, Product, format_date

Lazy Imports for Performance

If your package has expensive imports, defer them:

python
1# mypackage/__init__.py
2def __getattr__(name):
3    if name == "HeavyClass":
4        from mypackage.heavy_module import HeavyClass
5        return HeavyClass
6    raise AttributeError(f"module 'mypackage' has no attribute {name}")
7
8__all__ = ["HeavyClass"]
python
import mypackage  # Fast — HeavyClass not loaded yet
obj = mypackage.HeavyClass()  # Loads heavy_module on first access

This pattern (PEP 562) works in Python 3.7+.

Version and Metadata

python
1# mypackage/__init__.py
2__version__ = "1.2.3"
3__author__ = "Your Name"
4
5from mypackage.core import Client, Config
6
7__all__ = ["Client", "Config", "__version__"]
python
import mypackage
print(mypackage.__version__)  # "1.2.3"

Real-World Examples

Flask-style

python
1# flask/__init__.py
2from flask.app import Flask
3from flask.blueprints import Blueprint
4from flask.globals import request, session, g, current_app
5
6__all__ = ["Flask", "Blueprint", "request", "session", "g", "current_app"]

requests-style

python
1# requests/__init__.py
2from requests.api import get, post, put, delete, head, options, patch
3from requests.models import Response, Request
4from requests.sessions import Session
5
6__all__ = ["get", "post", "put", "delete", "head", "options", "patch",
7           "Response", "Request", "Session"]

What NOT to Put in init.py

python
1# BAD: Heavy computation at import time
2import pandas as pd
3data = pd.read_csv("large_file.csv")  # Runs when package is imported!
4
5# BAD: Side effects
6print("Package loaded!")  # Prints every time someone imports
7
8# BAD: Importing everything from every submodule
9from mypackage.module_a import *
10from mypackage.module_b import *
11from mypackage.module_c import *
12# Pollutes the namespace and causes name collisions

Common Pitfalls

  • Importing everything with from submodule import * in __init__.py: This pollutes the package namespace with every symbol from every submodule. Name collisions are silent — a later import overwrites an earlier one. Import only the public API explicitly.
  • Running expensive code at import time: Database connections, file reads, or network requests in __init__.py slow down every import of the package. Use lazy imports with __getattr__ or move initialization to an explicit init() function.
  • Circular imports between __init__.py and submodules: If __init__.py imports from module_a, and module_a imports from mypackage (which triggers __init__.py again), you get ImportError or partially initialized modules. Break the cycle by importing at function scope or restructuring.
  • Not defining __all__: Without __all__, from package import * exports everything in the namespace, including re-imported standard library modules and private helpers. Always define __all__ to control the public API.
  • Creating a namespace conflict with the package name: If mypackage/__init__.py defines mypackage = "something", it shadows the package itself. Avoid defining variables or functions with the same name as the package.

Summary

  • Use __init__.py to define the package's public API by re-exporting key symbols
  • Always define __all__ to control from package import * behavior
  • Keep __init__.py minimal — no heavy computation, no side effects
  • Use __getattr__ (Python 3.7+) for lazy imports of expensive modules
  • Import specific names, never from submodule import *, to avoid namespace pollution

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.