Python __init__.py,不僅僅是空檔案
許多Python開發者將__init__.py檔案僅用作"將此目錄識別為包的"用途的空檔案。但是這個檔案並不僅僅是一個標記(marker)。
__init__.py的核心是"當包被導入時首先執行的腳本"。
利用這一特性,可以清理混亂的代碼,明確地設計包的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__用來定義"只有這個列表中的內容才允許被帶走"的白名單。
# 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__)輕鬆檢查版本。
- 執行全域設置:
例如,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文檔'、'初始設置腳本'。善用這個檔案可以讓項目的結構更加清晰,易於維護。
目前沒有評論。