本文 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.pymodels.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 把共同依賴抽成第三個模組,或在函式內部再匯入。

最佳實踐

  1. 保持 __init__.py 輕量:只放說明文字、公共 API、簡易初始化。
  2. 使用 __all__ 明確定義公開介面,讓使用者一眼看出套件提供哪些功能。
  3. 採用相對匯入,避免因專案根目錄變動而出現找不到模組的錯誤。
  4. 加入型別提示(type hints),提升 IDE 補全與程式碼可讀性。
  5. 撰寫套件文件(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 內部匯入常用的 requestresponse 類別,提供簡潔的 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 風格 的程式庫,為未來的專案奠定堅實基礎。祝開發順利!