本文 AI 產出,尚未審核
Python 課程 – 模組與套件(Modules & Packages)
主題:套件結構與 __init__.py
簡介
在 Python 開發中,**套件(Package)**是組織程式碼的基礎單位。它不僅讓專案的檔案結構更清晰,也方便重複使用與分發。__init__.py 是套件的「入口檔案」,它決定了套件在被 import 時的行為。了解 __init__.py 的作用與寫法,能讓你在建構大型專案時,避免命名衝突、提升可維護性,甚至讓套件支援 相對匯入、自動執行初始化程式碼等進階功能。
本篇文章將從套件的基本目錄結構說起,深入說明 __init__.py 的多種用途,並提供實作範例、常見陷阱與最佳實踐,幫助初學者快速上手,同時給中級開發者提供可直接套用的技巧。
核心概念
1. 套件的最小結構
在 Python 中,只要目錄底下有 __init__.py,該目錄就被視為一個 套件。最簡單的範例:
my_pkg/
│ __init__.py
│ utils.py
│ models.py
my_pkg為套件名稱。utils.py、models.py為子模組(sub‑module)。
註:Python 3.3 之後,
__init__.py不是必須的,但仍建議保留,以確保相容性與可讀性。
2. __init__.py 的三大角色
| 角色 | 說明 | 範例 |
|---|---|---|
| 套件初始化 | 在套件被匯入時執行一次的程式碼,例如設定日誌、載入設定檔。 | logging.basicConfig(level=logging.INFO) |
| 匯出介面 | 定義 __all__,控制 from my_pkg import * 時哪些名稱會被匯出。 |
__all__ = ['utils', 'models'] |
| 相對匯入的橋樑 | 讓子模組能使用 from .module import … 形式的相對匯入。 |
from .utils import helper |
3. 實作範例
範例 1:基本套件與 __init__.py
my_pkg/__init__.py
# -*- coding: utf-8 -*-
"""my_pkg 套件的說明文件"""
# 匯出子模組,讓使用者可以直接 `import my_pkg.utils`
from . import utils, models
# 定義套件的公共 API
__all__ = ['utils', 'models']
my_pkg/utils.py
def hello(name: str) -> str:
"""回傳問候字串"""
return f"Hello, {name}!"
使用方式:
>>> from my_pkg import utils
>>> utils.hello('Alice')
'Hello, Alice!'
範例 2:套件初始化(設定日誌)
my_pkg/__init__.py
import logging
# 在套件被匯入時設定一次日誌
logging.basicConfig(
format='[%(levelname)s] %(asctime)s %(module)s: %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
logger.info("my_pkg 已成功載入")
my_pkg/models.py
from . import logger
class User:
def __init__(self, username: str):
self.username = username
logger.info(f"建立 User 物件: {username}")
執行:
>>> import my_pkg.models as m
[INFO] 2025-11-20 10:00:00 models: my_pkg 已成功載入
[INFO] 2025-11-20 10:00:01 models: 建立 User 物件: bob
範例 3:使用 __all__ 控制匯出
my_pkg/__init__.py
from .utils import hello
from .models import User
# 只允許 hello 與 User 被 * 匯入
__all__ = ['hello', 'User']
>>> from my_pkg import *
>>> hello('Tom')
'Hello, Tom!'
>>> User('Tom')
<User object at 0x...>
>>> # 以下名稱不會被匯入
>>> utils
NameError: name 'utils' is not defined
範例 4:子套件與相對匯入
目錄結構:
my_pkg/
│ __init__.py
│ utils.py
└─ sub_pkg/
│ __init__.py
│ worker.py
my_pkg/sub_pkg/__init__.py
# 讓外部可以直接 `from my_pkg.sub_pkg import worker`
from . import worker
my_pkg/sub_pkg/worker.py
# 相對匯入 utils
from ..utils import hello
def do_work(name):
print(hello(name))
使用方式:
>>> from my_pkg.sub_pkg import worker
>>> worker.do_work('Charlie')
Hello, Charlie!
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記 __init__.py |
沒有此檔案,Python 會把目錄當成普通資料夾,無法 import。 |
即使是空檔,也要放一個 __init__.py(可加入說明文字)。 |
| 相對匯入與絕對匯入混用 | 會導致 ImportError 或不易維護。 |
盡量在套件內部使用 相對匯入(from .module import …),在套件外部使用 絕對匯入(import my_pkg.module)。 |
__all__ 設定不當 |
* 匯入時漏掉重要 API,或暴露不該公開的名稱。 |
明確列出要公開的名稱,避免使用 __all__ = ['*']。 |
在 __init__.py 執行重量級運算 |
每次匯入套件都會執行,影響啟動速度。 | 把耗時的程式碼延遲到子模組或使用 lazy import。 |
| 循環匯入 | A 模組匯入 B,B 又匯入 A,會產生 ImportError。 |
把共同依賴抽成第三個模組,或在函式內部再匯入。 |
最佳實踐:
- 保持
__init__.py輕量:只放說明文字、公共 API、簡易初始化。 - 使用
__all__明確定義公開介面,讓使用者一眼看出套件提供哪些功能。 - 採用相對匯入,避免因專案根目錄變動而出現找不到模組的錯誤。
- 加入型別提示(type hints),提升 IDE 補全與程式碼可讀性。
- 撰寫套件文件(docstring),配合
pydoc或 Sphinx 產生 API 文件。
實際應用場景
| 場景 | 套件結構 | 為何需要 __init__.py |
|---|---|---|
資料科學工具箱(如 my_ml) |
my_ml/ 包含 preprocess/、models/、utils.py |
在根目錄設定全域日誌、載入設定檔,讓子套件共享同一個 config 物件。 |
Web 框架(如 my_web) |
my_web/ 包含 router/、middleware/、templates/ |
__init__.py 內部匯入常用的 request、response 類別,提供簡潔的 from my_web import request 介面。 |
CLI 工具(如 my_cli) |
my_cli/ 包含 commands/、core.py |
在 __init__.py 中設定 argparse 解析器,並透過 __all__ 暴露 main 函式,讓 python -m my_cli 可直接執行。 |
多語系套件(如 i18n_pkg) |
i18n_pkg/ 包含 locales/、loader.py |
__init__.py 讀取環境變數決定預設語系,並提供 gettext 函式給外部使用。 |
總結
__init__.py是套件的「門面」與「守門員**,負責初始化、匯出介面與相對匯入的橋樑**。- 建立套件時,最小結構只需要一個空的
__init__.py,但最佳實踐建議在裡面加入說明文件、公共 API (__all__) 以及輕量的初始化程式碼。 - 透過 相對匯入、
__all__與 型別提示,可以讓套件更容易維護、使用者更友善。 - 注意常見陷阱(忘記
__init__.py、循環匯入、重量級初始化),並遵守最佳實踐,可讓你的 Python 專案在規模擴大時仍保持清晰與高效。
掌握了套件結構與 __init__.py 的運用,你就能設計出 可擴充、易維護且符合 Pythonic 風格 的程式庫,為未來的專案奠定堅實基礎。祝開發順利!