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.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文檔'、'初始設置腳本'。善用這個檔案可以讓項目的結構更加清晰,易於維護。