sec05 - Appendix 3:Understanding Python Package Structure: Proper Use of __init__.py and __all__

In this lecture, we will explain the role of __init__.py, which is essential for organizing the structure of Python packages.

We will also clarify topics that often confuse beginners, such as the behavior of import * and the usage of __all__, using concrete examples.

スポンサーリンク

What Is __init__.py? Understanding the Basic Structure of Python Packages

__init__.py is a special file that tells Python “this folder is a package.”

For example, consider the following structure:

project/
├─ main.py
└─ my_package/
   ├ __init__.py
   ├ module_a.py
   └ module_b.py

The presence of __init__.py inside my_package/ allows the following imports written in main.py:

import my_package.module_a
from my_package import module_b

If __init__.py does not exist, Python treats the folder as “just a directory” and does not recognize it as a package.

Why __init__.py Became Optional in Python 3.3+: The Concept of Implicit Namespace Packages

Strictly speaking, Python 3.3 and later includes a mechanism called Implicit Namespace Packages, allowing a folder to function as a package even without __init__.py. However, this is mainly for advanced use cases, and many tools (linters and IDEs) assume the presence of __init__.py. Therefore, for readability and compatibility, adding this file is strongly recommended.

What Should You Write in __init__.py? Fundamentals and Practical Examples

Please download the following file and check the contents inside each file:


The contents of __init__.py may be completely empty. However, as a project grows, it is increasingly common to include initialization logic or convenient export settings within the file.

Organizing a Package's Public API Using __init__.py

For example, frequently used classes or functions can be gathered here so that the package can present a clean and unified interface.

# __init__.py
from .module_a import func_a
from .module_b import ClassB

This allows external files (like main.py) to access them in a simple manner:

from my_package import func_a, ClassB

How __init__.py Influences the Import Mechanism

from my_package import func_a, ClassB works due to the following sequence of operations:

  1. The moment you write from my_package, the __init__.py inside my_package is executed.
  2. Inside __init__.py, the statements from .module_a import func_a and from .module_b import ClassB run, exposing func_a and ClassB as attributes of the my_package namespace.
  3. As a result, main.py can simply write from my_package import func_a, ClassB and import them directly.
スポンサーリンク

How to Use __all__: Controlling import * Safely

When writing code inside __init__.py, the special variable __all__ becomes important. __all__ controls which symbols (functions, classes, variables, etc.) are exported when executing from package import *.

The wildcard * means “everything,” but it does not actually import every module inside the package automatically.

If __all__ is not defined, import * imports the following:

  1. Variables, functions, and classes defined directly inside __init__.py.
  2. Names imported inside __init__.py whose names do not start with an underscore (_).

Without defining __all__, internal or intermediate variables may be unintentionally exposed, potentially polluting namespaces in importing code or introducing unexpected bugs.

By defining the list variable __all__, you can explicitly control which names (symbols) will be exported when import * is used. This prevents unintended imports and greatly improves both the maintainability and the clarity of the package’s API.

# __init__.py
from .module_a import func_a
from .module_b import ClassB

__all__ = ['func_a', 'ClassB']

Now, even when using wildcard import, only func_a and ClassB will be imported:

# main.py
from my_package import *

func_a()
obj = ClassB()

Writing Initialization Logic in __init__.py

Another use case of __init__.py is writing initialization logic that executes when the package is loaded.

For example, the message/__init__.py in fortune_cookie can register messages used for language selection.

# message/__init__.py
PROMPT_SELECT_LANGUAGE = 'Please select your language (jp/en): '

This can be called from fortune.py as follows:

# fortune.py
import message

lang_code = input(message.PROMPT_SELECT_LANGUAGE)
print(lang_code)

Summary: Best Practices for Using __init__.py

Finally, let’s summarize the best practices for organizing package structures.

  1. Always place an __init__.py file in every package.
  2. Import frequently used functions and classes inside __init__.py so users do not need to know the internal structure.
  3. Define __all__ to explicitly control which APIs are exposed externally.
  4. Keep initialization logic minimal and maintain a clean structure.

By following these practices, the overall clarity and maintainability of your package design will improve significantly.

スポンサーリンク