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.py的CancelView。
- 坏方法(循环引用的风险):
尝试在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文档’、‘初始设置脚本’,是一个功能强大的工具。合理利用这个文件,可以使项目结构变得更加整洁和易于维护。
目前没有评论。