Python __init__.py, More Than Just an Empty File
Many Python developers use the __init__.py file as a blank file that only serves the purpose of "making this directory recognized as a package." However, this file is not just a marker.
The core of __init__.py is that it is the “script that runs first when the package is imported”.
By utilizing this feature, you can perform powerful tasks such as organizing messy code, clearly designing the package's API, and managing initialization logic.
1. Tidying Up Messy Import Paths (API Simplification)
This is the most useful and common way to use __init__.py. It allows classes or functions from submodules to be 'pulled up' to the package level, making it easier for users to import them.
Problem:
Let’s assume that Django’s views.py has over 1,000 lines of code and is separated into different files by functionality.
payments/
├── views/
│ ├── __init__.py
│ ├── user_views.py # UserProfileView, ...
│ ├── payment_views.py # PaymentView, ...
│ └── webhooks.py # PaypalWebhookView, ...
└── urls.py
Using these views in urls.py becomes very cumbersome.
# payments/urls.py (bad example)
from .views import user_views, payment_views, webhooks
urlpatterns = [
path('profile/', user_views.UserProfileView.as_view()),
path('toss/', payment_views.PaymentView.as_view()),
path('webhook/', webhooks.PaypalWebhookView.as_view()),
]
urls.py must know the entire internal file structure of the views directory.
Solution (using init.py):
Import the necessary views in the init.py file of views beforehand.
# payments/views/__init__.py
# Pull the necessary classes/functions to the package level from each file.
from .user_views import UserProfileView
from .payment_views import PaymentView
from .webhooks import PaypalWebhookView
Now, urls.py that uses the views package looks as if the views package directly contains UserProfileView.
# payments/urls.py (improved example)
# 'views' is now a package (directory), not just a file.
from . import views
urlpatterns = [
# Thanks to __init__.py, you don’t need to know the internal structure.
path('profile/', views.UserProfileView.as_view()),
path('toss/', views.PaymentView.as_view()),
path('webhook/', views.PaypalWebhookView.as_view()),
]
Whether views.py is a file or a directory with multiple files, the code in urls.py remains the same. This is good abstraction.
2. Defining the 'Official API' of the Package (__all__)
from my_package import * (wildcard import) is convenient, but it can unintentionally bring in variables or modules (e.g. import os) that can clutter the current namespace.
__all__ defines a whitelist so that when import * is executed, “only things in this list can be imported”.
# payments/views/__init__.py
# Modules for internal use
import logging
from .internal_utils import _helper_function
# Exposed views
from .user_views import UserProfileView
from .payment_views import PaymentView
# Define __all__:
# "If you use import *, only UserProfileView and PaymentView can be imported."
__all__ = [
'UserProfileView',
'PaymentView'
]
Now, even if you execute from payments.views import *, the logging module or the internal function _helper_function will not be imported, keeping the namespace safe.
3. Package Initialization and Metadata Definition
This is a perfect place to put code that needs to run only once when the package is first loaded.
- Providing version information:
# my_library/__init__.py
__version__ = "1.2.0"
__author__ = "whitedec"
Now users can easily check the version with import my_library; print(my_library.__version__).
- Executing global setup:
Here’s an example that ensures that the Celery app is loaded together when Django starts by internally calling django.setup(), which can be included in init.py.
# my_project/__init__.py (Celery example)
# Ensure that the Celery app is loaded when Django starts
from .celery import app as celery_app
__all__ = ('celery_app',)
※ Note: Circular References Among Internal Modules
When using __init__.py as a 'front desk', there is something to be careful about.
Let’s assume that views/webhooks.py needs to use the CancelView from views/payment_commons.py.
- Bad Method (Risk of circular reference):
Trying to import using from . import CancelView (i.e., through init.py) in webhooks.py.
- Good Method (Direct Import):
Like from .payment_commons import CancelView directly from the necessary module in webhooks.py.
Rule:
- External users (e.g.
urls.py): Use the API through the 'front desk' (__init__.py). - Internal modules (e.g.
webhooks.py): Import functionality directly from peer modules (e.g.payment_commons.py) without going through the 'front desk'.
Conclusion
__init__.py is not just a simple empty file; it is a powerful tool that can serve as the package's 'front desk', 'official API documentation', and 'initial setup script'. By utilizing this file well, you can make the structure of your project much cleaner and easier to maintain.
There are no comments.