3.1 pathlib - 路徑操作
3.1 pathlib - 路徑操作
pathlib 是 Python 3.4+ 引入的現代路徑處理模組,提供物件導向的 API 來處理檔案系統路徑。在 Hook 系統中,幾乎每個檔案都使用 pathlib。
為什麼使用 pathlib?
傳統 os.path 方式
1import os
2
3# 組合路徑
4config_path = os.path.join(project_root, ".claude", "config.json")
5
6# 取得父目錄
7parent = os.path.dirname(file_path)
8
9# 取得檔名
10filename = os.path.basename(file_path)
11
12# 檢查存在
13if os.path.exists(config_path):
14 ...現代 pathlib 方式
1from pathlib import Path
2
3# 組合路徑
4config_path = project_root / ".claude" / "config.json"
5
6# 取得父目錄
7parent = file_path.parent
8
9# 取得檔名
10filename = file_path.name
11
12# 檢查存在
13if config_path.exists():
14 ...基本操作
建立 Path 物件
1from pathlib import Path
2
3# 從字串建立
4p = Path("/home/user/project")
5
6# 當前目錄
7cwd = Path.cwd()
8
9# 使用者目錄
10home = Path.home()
11
12# 從 __file__ 建立
13current_file = Path(__file__)路徑組合
使用 / 運算子組合路徑(非常直觀):
1from pathlib import Path
2
3project = Path("/home/user/project")
4config = project / ".claude" / "config.json"
5
6# 等同於
7config = project.joinpath(".claude", "config.json")取得路徑部分
1p = Path("/home/user/project/file.txt")
2
3p.name # "file.txt"
4p.stem # "file"(不含副檔名)
5p.suffix # ".txt"
6p.parent # Path("/home/user/project")
7p.parents[0] # Path("/home/user/project")
8p.parents[1] # Path("/home/user")
9p.parts # ('/', 'home', 'user', 'project', 'file.txt')實際範例:Hook 系統
日誌目錄建立
來自 .claude/lib/hook_logging.py:
1from pathlib import Path
2
3def setup_hook_logging(hook_name: str) -> logging.Logger:
4 # 建立日誌目錄
5 project_root = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
6 log_dir = Path(project_root) / ".claude" / "hook-logs" / hook_name
7
8 # mkdir 的 parents=True 會建立所有不存在的父目錄
9 # exist_ok=True 表示如果目錄已存在不會報錯
10 log_dir.mkdir(parents=True, exist_ok=True)
11
12 # 日誌檔案路徑
13 timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
14 log_file = log_dir / f"{hook_name}-{timestamp}.log"
15
16 # ...設定檔案搜尋
來自 .claude/lib/config_loader.py:
1def load_config(config_name: str) -> dict:
2 config_dir = get_config_dir()
3
4 # 優先嘗試不同副檔名
5 yaml_path = config_dir / f"{config_name}.yaml"
6 yml_path = config_dir / f"{config_name}.yml"
7 json_path = config_dir / f"{config_name}.json"
8
9 if yaml_path.exists():
10 return _load_yaml_file(yaml_path)
11 elif yml_path.exists():
12 return _load_yaml_file(yml_path)
13 elif json_path.exists():
14 return _load_json_file(json_path)
15
16 raise FileNotFoundError(f"Configuration not found: {config_name}")檔案操作
讀取檔案
1from pathlib import Path
2
3p = Path("config.json")
4
5# 讀取文字
6content = p.read_text(encoding="utf-8")
7
8# 讀取位元組
9data = p.read_bytes()寫入檔案
1from pathlib import Path
2
3p = Path("output.txt")
4
5# 寫入文字
6p.write_text("Hello, World!", encoding="utf-8")
7
8# 寫入位元組
9p.write_bytes(b"binary data")檢查檔案類型
1p = Path("/some/path")
2
3p.exists() # 是否存在
4p.is_file() # 是否為檔案
5p.is_dir() # 是否為目錄
6p.is_symlink() # 是否為符號連結目錄操作
建立目錄
1from pathlib import Path
2
3p = Path("new_dir/sub_dir")
4
5# 建立目錄(包含父目錄)
6p.mkdir(parents=True, exist_ok=True)列出目錄內容
1from pathlib import Path
2
3p = Path(".")
4
5# 列出所有項目
6for item in p.iterdir():
7 print(item)
8
9# 使用 glob 模式
10for py_file in p.glob("*.py"):
11 print(py_file)
12
13# 遞迴搜尋
14for md_file in p.rglob("*.md"):
15 print(md_file)實際範例:驗證所有 Hook
來自 .claude/lib/hook_validator.py:
1def validate_all_hooks(self, hooks_dir: Optional[str] = None):
2 if hooks_dir is None:
3 hooks_dir = str(self.project_root / ".claude" / "hooks")
4
5 hooks_dir = self._resolve_path(hooks_dir)
6
7 # 找出所有 .py 檔案
8 results = []
9 for hook_file in sorted(hooks_dir.glob("*.py")):
10 if hook_file.name.startswith("_"):
11 continue # 跳過 __init__.py 等
12 results.append(self.validate_hook(str(hook_file)))
13
14 return results路徑解析
相對路徑與絕對路徑
1from pathlib import Path
2
3p = Path("./relative/path")
4
5# 轉換為絕對路徑
6absolute = p.resolve()
7
8# 相對於某個目錄
9relative = p.relative_to(Path.cwd())實際範例:解析路徑
1def _resolve_path(self, path: str) -> Path:
2 """解析路徑為絕對路徑"""
3 p = Path(path)
4 if p.is_absolute():
5 return p
6 return self.project_root / p常用模式
計算腳本所在目錄
1# 在 .claude/hooks/my_hook.py 中
2from pathlib import Path
3
4# 取得 lib 目錄
5lib_path = Path(__file__).parent.parent / "lib"
6# __file__ = .claude/hooks/my_hook.py
7# parent = .claude/hooks/
8# parent.parent = .claude/
9# / "lib" = .claude/lib/確保檔案副檔名
1def ensure_extension(path: Path, ext: str) -> Path:
2 """確保檔案有指定的副檔名"""
3 if path.suffix != ext:
4 return path.with_suffix(ext)
5 return path
6
7# 使用
8p = Path("config")
9p = ensure_extension(p, ".json") # Path("config.json")安全的檔案讀取
1def safe_read_file(path: Path) -> Optional[str]:
2 """安全讀取檔案,不存在時返回 None"""
3 if not path.exists():
4 return None
5 try:
6 return path.read_text(encoding="utf-8")
7 except Exception:
8 return None思考題
Path("/a/b") / "c"和Path("/a/b").joinpath("c")有什麼區別?- 為什麼
mkdir(parents=True, exist_ok=True)是常見的組合? glob("**/*.py")和rglob("*.py")有什麼區別?
實作練習
- 寫一個函式,找出目錄中所有超過 1MB 的檔案
- 寫一個函式,將所有
.txt檔案重命名為.md - 實作一個函式,計算目錄中所有 Python 檔案的總行數
下一章:json - 序列化