Python __init__.py,不只是空文件



许多Python开发者将__init__.py文件仅用作“让这个目录被识别为包”的空文件。但这个文件并不只是一个标记(marker)。

__init__.py的核心在于“在包被import时最先执行的脚本”这一点。

利用这个特性,可以整理混乱的代码,清晰地设计包的API,管理初始化逻辑,执行非常强大的操作。


1. 整理混乱的Import路径 (简化API)

__init__.py是最有用、最常见的应用方式。将子模块的类或函数“提升”到包级别,使用户可以更方便地导入。

问题所在:

假设Django的views.py已经超过1,000行,并按功能将文件分开。

payments/
├── views/
│   ├── __init__.py
│   ├── user_views.py    # UserProfileView, ...
│   ├── payment_views.py # PaymentView, ...
│   └── webhooks.py      # PaypalWebhookView, ...
└── urls.py

urls.py中使用这些视图非常麻烦。

# payments/urls.py (坏例子)
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必须知道views目录的内部文件结构。


解决方案 (init.py应用):

在views/init.py文件中提前导入所需的视图。

# payments/views/__init__.py

# 从各自文件中提取所需的类/函数到包级别。
from .user_views import UserProfileView
from .payment_views import PaymentView
from .webhooks import PaypalWebhookView

现在使用views包的urls.py看起来就像views包直接拥有UserProfileView一样。

# payments/urls.py (改进的例子)

# 'views'现在是一个包(目录)而不是文件。
from . import views

urlpatterns = [
    # 多亏了__init__.py,我们不需要知道内部结构。
    path('profile/', views.UserProfileView.as_view()),
    path('toss/', views.PaymentView.as_view()),
    path('webhook/', views.PaypalWebhookView.as_view()),
]

无论views.py是文件还是有多个文件的目录,urls.py的代码保持不变。这就是良好的抽象


2. 明确包的‘官方API’ (__all__)



from my_package import *(通配符导入)虽然方便,但可能意外地引入变量或模块(例如import os),污染当前命名空间。

__all__定义了一个“只允许导入此列表中的东西”的白名单,当进行import *时。

# payments/views/__init__.py

# 内部使用的模块
import logging 
from .internal_utils import _helper_function

# 对外公开的视图
from .user_views import UserProfileView
from .payment_views import PaymentView

# __all__定义:
# "使用import *时,只能引入UserProfileView和PaymentView."
__all__ = [
    'UserProfileView',
    'PaymentView'
]

现在即使执行from payments.views import *logging模块或内部函数_helper_function也不会被导入,从而安全地保护命名空间。


3. 包的初始化和元数据定义

这是放置只需执行一次的代码的理想地点。

  • 提供版本信息:
# my_library/__init__.py
__version__ = "1.2.0"
__author__ = "whitedec"

现在用户可以轻松通过import my_library; print(my_library.__version__)检查版本。

  • 执行全局设置 (Setup):

当Django导入django时内部调用django.setup(),或者Celery的应用初始化代码被放入init.py也是典型例子。


# my_project/__init__.py (Celery示例)

# 确保Celery应用在Django启动时也一起加载
from .celery import app as celery_app

__all__ = ('celery_app',)


※ 注意:内部模块之间的循环引用

当将__init__.py用作“导览台”时,有一些注意事项。

假设views/webhooks.py需要使用views/payment_commons.pyCancelView

  • 坏方法(循环引用的风险):

尝试在webhooks.py中通过from . import CancelView(即init.py)来引入。

  • 好方法(直接导入):

在webhooks.py中直接从所需模块from .payment_commons import CancelView引入。

规则:

  • 外部用户(例如urls.py:通过“导览台”(__init__.py)使用API。
  • 内部模块(例如webhooks.py:直接从同级模块(payment_commons.py)中引入功能,而不经过“导览台”。

结论

__init__.py不仅是一个简单的空文件,更是包的‘导览台’、‘官方API文档’、‘初始设置脚本’,是一个功能强大的工具。合理利用这个文件,可以使项目结构变得更加整洁和易于维护。