1.3 模組與套件組織
1.3 模組與套件組織
實際範例:Hook 系統的
相對導入(使用
Python 的模組系統是組織程式碼的基礎。理解模組如何運作,是維護和擴展 Hook 系統的關鍵。
承接提示:如果你還不熟悉程式如何從單一
.py檔案拆成多個 module,請先閱讀 從單一 script 到多檔案專案。本章聚焦在已經出現 package 後,如何理解 module、__init__.py與公開 API。
基本概念
模組(Module)
一個 .py 檔案就是一個模組。例如 git_utils.py 就是 git_utils 模組。
1# git_utils.py 是一個模組
2# 可以被其他檔案導入
3
4from git_utils import get_current_branch套件(Package)
包含 __init__.py 的目錄就是一個套件。套件可以包含多個模組。
1.claude/lib/
2├── __init__.py # 使 lib 成為套件
3├── git_utils.py # 模組
4├── hook_io.py # 模組
5├── hook_logging.py # 模組
6└── config_loader.py # 模組__init__.py 的作用
__init__.py 是套件的初始化檔案,它在套件被導入時執行。
實際範例:Hook 系統的 __init__.py
來自 .claude/lib/__init__.py:
1"""
2Claude Hooks 共用程式庫
3
4提供 Hook 腳本共用的工具函式,消除程式碼重複。
5
6模組結構:
7- git_utils: Git 操作工具(分支、worktree、專案根目錄)
8- hook_logging: Hook 日誌系統
9- hook_io: Hook 輸入輸出處理
10"""
11
12# 從子模組導入並重新匯出
13from .git_utils import (
14 run_git_command,
15 get_current_branch,
16 get_project_root,
17 get_worktree_list,
18 is_protected_branch,
19 is_allowed_branch,
20)
21
22from .hook_logging import setup_hook_logging
23
24from .hook_io import (
25 read_hook_input,
26 write_hook_output,
27 create_pretooluse_output,
28 create_posttooluse_output,
29)
30
31from .config_loader import (
32 load_config,
33 load_agents_config,
34 load_quality_rules,
35 clear_config_cache,
36)
37
38# 定義公開 API
39__all__ = [
40 # git_utils
41 "run_git_command",
42 "get_current_branch",
43 # ... 省略其他
44]
45
46__version__ = "0.28.0"__init__.py 的三個主要功能
1. 宣告套件身份
空的 __init__.py 也能讓目錄成為套件:
1# lib/__init__.py(最簡形式)
2# 即使是空檔案,也使 lib 成為套件2. 定義公開 API
透過 __all__ 列表控制 from package import * 的行為:
1__all__ = [
2 "run_git_command",
3 "get_current_branch",
4 "setup_hook_logging",
5]
6
7# 當使用者執行 from lib import * 時
8# 只會導入 __all__ 中列出的名稱3. 簡化導入路徑
使用者可以直接從套件導入,而不需要知道子模組:
1# 有 __init__.py 的重新匯出:簡潔
2from lib import get_current_branch, setup_hook_logging
3
4# 沒有 __init__.py 的重新匯出:冗長
5from lib.git_utils import get_current_branch
6from lib.hook_logging import setup_hook_logging相對導入與絕對導入
相對導入(使用 .)
在套件內部使用相對導入:
1# 在 lib/config_loader.py 中
2from .git_utils import get_project_root # 同級模組
3from . import hook_io # 導入整個模組絕對導入
從套件外部或在腳本中使用絕對導入:
1# 在 .claude/hooks/some_hook.py 中
2import sys
3sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
4
5from git_utils import get_current_branch # 絕對導入
6from hook_io import read_hook_input套件版本管理
在 __init__.py 中定義版本號是常見做法:
1__version__ = "0.28.0"這樣使用者可以查詢版本:
1import lib
2print(lib.__version__) # "0.28.0"模組載入順序
Python 搜尋模組的順序:
- 內建模組
sys.path[0](腳本所在目錄)PYTHONPATH環境變數- 標準庫
- site-packages(第三方套件)
實際範例:Hook 腳本的路徑設定
1#!/usr/bin/env python3
2"""Branch Verify Hook"""
3
4import sys
5from pathlib import Path
6
7# 將 lib 目錄加入搜尋路徑
8sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
9
10# 現在可以導入 lib 中的模組
11from git_utils import get_current_branch
12from hook_io import read_hook_input, write_hook_output最佳實踐
1. 避免循環導入
1# 不好:a.py 和 b.py 互相導入
2# a.py
3from b import func_b
4# b.py
5from a import func_a # 循環導入!
6
7# 好:重構為第三個模組
8# common.py
9def shared_function(): ...
10# a.py
11from common import shared_function
12# b.py
13from common import shared_function2. 延遲導入
對於可選依賴或避免循環導入:
1def load_yaml_config():
2 # 只在需要時才導入
3 try:
4 import yaml
5 return yaml.safe_load(...)
6 except ImportError:
7 # 備案方案
8 import json
9 return json.load(...)3. 清晰的模組邊界
每個模組應該有單一、明確的職責:
1lib/
2├── git_utils.py # Git 操作(單一職責)
3├── hook_io.py # 輸入輸出處理(單一職責)
4├── hook_logging.py # 日誌系統(單一職責)
5└── config_loader.py # 配置載入(單一職責)思考題
- 為什麼
__init__.py使用from .git_utils import ...而不是from git_utils import ...? - Hook 腳本中的
sys.path.insert(0, ...)為什麼使用0作為索引? __all__列表的作用是什麼?如果不定義會怎樣?
實作練習
閱讀 .claude/lib/__init__.py,回答以下問題:
- 這個套件匯出了多少個公開函式?
- 套件的版本號是多少?
- 如果要新增一個
utils.py模組並匯出format_message函式,需要修改哪些地方?
上一章:Python 哲學與設計理念 下一章:導入機制與路徑管理