Python gives you a number of options for exporting the public portions of your package’s API depending on the needs of your users. These are exposed via __init__.py. This allows the developer to have a module structure that is optimized for developers while exposing an API that is optimal for the end user.

Given the following example package with 3 modules:

/src  
    /example_pkg  
        __init__.py  
        foo.py  
        bar.py  
        baz.py  
    setup.py  
    README.md  
    LICENSE

And the functions from each module:

## foo.py
def foo_func():  
    print(this is a foo function)

## bar.py
def bar_func():  
    print(this is a bar function)

## baz.py
def baz_func():  
    print(this is a baz function)

You can take a few different approaches, each of which have their own advantages and disadvantages, which are outlined in the source article. This serves as a quick references

The Naive Approach#

By leaving __init__.py empty, you expose the underlying module structure to the user. This will expose all functions in all modules in the package.

import example_pkg
example_pkg.foo.foo_func()

## or
from example_pkg import bar
bar.bar_func()

## or
import example_pkg.baz as ex_baz
ex_baz.baz_func()

You can achieve the same flexibility for limited modules by adding the modules you want to expose to __init__.py:

import example_pkg.foo
import example_pkg.bar
import example_pkg.baz

Make All Functions Public Under Package#

This enables your user to access all functions in the module(s) you make public but under the package’s namespace instead of the module’s within the package.

__init__.py

from .foo import *
from .bar import *
from .baz import *

Calling the functions:

import example_pkg

example_pkg.foo_func()
example_pkg.bar_func()
example_pkg.baz_func()

This has the added advantage of hiding module functions marked private with a single underscore due to the import *.

Make Specific Functions Public Under Package#

This enables you to explicitly define which functions are made public by your module. This method is the least flexible for the user, but for smaller packages allows the developer to create a clean interface for users. This doesn’t scale well for larger packages, however.

__init__.py

from .foo import foo_func  
from .bar import bar_func  
from .baz import baz_func

Calling the functions:

import example_pkg

example_pkg.foo_func()  
example_pkg.bar_func()  
example_pkg.baz_func()

Control import * Explicitly with __all__#

The wildcard approaches above rely on naming conventions (the single-underscore prefix) to decide what from .foo import * re-exports. __all__ makes that contract explicit instead of implicit: it’s a list of strings naming exactly the public symbols, and it overrides the underscore heuristic entirely.

In a module (foo.py), __all__ defines what import * pulls in:

## foo.py
__all__ = ["foo_func"]

def foo_func():
    print("this is a foo function")

def _helper():  # excluded from `import *` regardless of __all__
    ...

In a package’s __init__.py, __all__ controls which names from example_pkg import * exposes — useful for curating the flattened API from the “Make All Functions Public” approach without leaking helpers:

## __init__.py
from .foo import *
from .bar import *
from .baz import *

__all__ = ["foo_func", "bar_func", "baz_func"]

Notes and caveats:

  • __all__ only affects import *. Explicit imports (from example_pkg import _helper) still work — it’s a convention for the public surface, not an access control mechanism.
  • If __all__ is absent, import * falls back to importing every name not prefixed with an underscore.
  • Listing a name in __all__ that doesn’t exist raises AttributeError at import time, so it doubles as a lightweight check that your public API names are spelled correctly.
  • For packages, names in __all__ can be submodules as well as functions/classes, in which case import * imports those submodules.

Sources#