一. 前言 #
嗨,大家好!我是拍拍君 🎉
你有沒有寫過這樣的程式碼?
import os
base_dir = "/home/pypy君/projects"
data_dir = os.path.join(base_dir, "data")
file_path = os.path.join(data_dir, "report.csv")
if os.path.exists(file_path):
with open(file_path, "r") as f:
content = f.read()
filename = os.path.basename(file_path)
extension = os.path.splitext(file_path)[1]
parent = os.path.dirname(file_path)
一堆 os.path.join、os.path.exists、os.path.basename⋯⋯光看就覺得累 😵
Python 3.4 開始,標準庫就內建了一個超優雅的模組:pathlib。它把檔案路徑變成物件,讓你可以用直覺的方式操作路徑,程式碼瞬間變得乾淨又好讀!
上面那段程式碼用 pathlib 改寫:
from pathlib import Path
base_dir = Path("/home/pypy君/projects")
file_path = base_dir / "data" / "report.csv"
if file_path.exists():
content = file_path.read_text()
filename = file_path.name
extension = file_path.suffix
parent = file_path.parent
是不是清爽很多?用 / 運算子串接路徑,用屬性取得檔名,用方法讀寫檔案——一切都那麼自然。
今天拍拍君就帶大家徹底學會 pathlib!
二. 安裝 #
好消息:不用安裝! pathlib 是 Python 3.4+ 的標準庫模組,直接 import 就能用:
from pathlib import Path
如果你還在用 Python 2⋯⋯拜託趕快升級吧 😂
三. 建立路徑物件 #
基本建立 #
from pathlib import Path
# 從字串建立
p = Path("/Users/pypy君/Documents")
# 當前目錄
cwd = Path.cwd()
print(cwd) # /Users/pypy君/projects
# 家目錄
home = Path.home()
print(home) # /Users/pypy君
用 / 運算子串接
#
這是 pathlib 最經典的用法——用除法符號串接路徑:
project = Path.home() / "code" / "my_project"
config = project / "config" / "settings.yaml"
print(project) # /Users/pypy君/code/my_project
print(config) # /Users/pypy君/code/my_project/config/settings.yaml
比起 os.path.join(os.path.join(base, "config"), "settings.yaml") 簡潔太多了!
跨平台自動處理 #
pathlib 會根據你的作業系統自動使用正確的路徑分隔符:
# 在 macOS/Linux 上
p = Path("data") / "output" / "result.csv"
print(p) # data/output/result.csv
# 在 Windows 上(同樣的程式碼)
# data\output\result.csv
你再也不用煩惱 / 還是 \ 的問題了!
四. 路徑屬性:拆解路徑的各個部分 #
Path 物件提供了超多好用的屬性,讓你輕鬆取得路徑的各個組成部分:
p = Path("/Users/pypy君/projects/data/report_2026.csv")
print(p.name) # report_2026.csv (完整檔名)
print(p.stem) # report_2026 (不含副檔名的檔名)
print(p.suffix) # .csv (副檔名)
print(p.suffixes) # ['.csv'] (所有副檔名)
print(p.parent) # /Users/pypy君/projects/data (上層目錄)
print(p.anchor) # / (根目錄)
print(p.parts) # ('/', 'Users', 'pypy君', 'projects', 'data', 'report_2026.csv')
多重副檔名 #
有些檔案有多個副檔名(像 .tar.gz),pathlib 也能處理:
archive = Path("backup.tar.gz")
print(archive.suffix) # .gz
print(archive.suffixes) # ['.tar', '.gz']
print(archive.stem) # backup.tar
修改路徑的部分 #
p = Path("/data/old_report.csv")
# 改檔名
new_p = p.with_name("new_report.csv")
print(new_p) # /data/new_report.csv
# 改副檔名
json_p = p.with_suffix(".json")
print(json_p) # /data/old_report.json
# 改 stem(Python 3.9+)
renamed = p.with_stem("final_report")
print(renamed) # /data/final_report.csv
五. 檔案操作:讀寫就是這麼簡單 #
讀取檔案 #
不用再寫 with open(...) as f 了(當然,大檔案還是建議用):
p = Path("config.yaml")
# 讀取文字
text = p.read_text(encoding="utf-8")
# 讀取二進位
data = p.read_bytes()
寫入檔案 #
output = Path("result.txt")
# 寫入文字
output.write_text("拍拍君到此一遊!\n", encoding="utf-8")
# 寫入二進位
output.write_bytes(b"\x89PNG...")
⚠️ 注意:
write_text()和write_bytes()會覆蓋原有內容!如果要追加,還是得用open(p, "a")的方式。
建立與刪除 #
# 建立目錄
data_dir = Path("output") / "2026" / "02"
data_dir.mkdir(parents=True, exist_ok=True)
# parents=True → 自動建立中間目錄
# exist_ok=True → 目錄已存在也不會報錯
# 建立空檔案(類似 touch)
new_file = data_dir / "placeholder.txt"
new_file.touch()
# 刪除檔案
new_file.unlink()
# 刪除空目錄
data_dir.rmdir()
💡 如果要刪除非空目錄,需要搭配
shutil.rmtree(),pathlib本身只能刪空目錄。
六. 遍歷與搜尋:找到你要的檔案 #
列出目錄內容 #
project = Path("my_project")
# 列出直接子項目
for item in project.iterdir():
print(item)
# 只列出檔案
files = [f for f in project.iterdir() if f.is_file()]
# 只列出目錄
dirs = [d for d in project.iterdir() if d.is_dir()]
glob 模式搜尋 #
glob() 是 pathlib 的殺手級功能之一:
project = Path("my_project")
# 找所有 Python 檔案
py_files = list(project.glob("*.py"))
# 找所有子目錄中的 Python 檔案
all_py = list(project.glob("**/*.py"))
# 找所有圖片
images = list(project.glob("**/*.png")) + list(project.glob("**/*.jpg"))
# 找特定命名模式
logs = list(project.glob("log_202?.txt")) # log_2024.txt, log_2025.txt...
rglob:遞迴搜尋的捷徑 #
rglob() 等同於 glob("**/pattern"),更簡潔:
# 這兩行是等價的
project.glob("**/*.py")
project.rglob("*.py")
# 找所有 Markdown 檔案
docs = list(Path("notes").rglob("*.md"))
七. 路徑判斷:這是什麼? #
p = Path("some_path")
p.exists() # 路徑是否存在
p.is_file() # 是否為檔案
p.is_dir() # 是否為目錄
p.is_symlink() # 是否為符號連結
p.is_absolute() # 是否為絕對路徑
實用範例:安全地處理檔案 #
def safe_read(path: str | Path) -> str | None:
"""安全讀取檔案,不存在就回傳 None"""
p = Path(path)
if not p.exists():
print(f"⚠️ 檔案不存在:{p}")
return None
if not p.is_file():
print(f"⚠️ 不是檔案:{p}")
return None
return p.read_text(encoding="utf-8")
八. 實戰:用 pathlib 整理專案檔案 #
來看一個完整的實戰例子——整理下載資料夾裡的檔案:
from pathlib import Path
from datetime import datetime
def organize_downloads(download_dir: str = "~/Downloads"):
"""依照副檔名分類檔案到子資料夾"""
downloads = Path(download_dir).expanduser()
# 分類規則
categories = {
"圖片": {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"},
"文件": {".pdf", ".doc", ".docx", ".txt", ".md"},
"程式碼": {".py", ".js", ".ts", ".html", ".css", ".json"},
"壓縮檔": {".zip", ".tar", ".gz", ".7z", ".rar"},
"影片": {".mp4", ".mov", ".avi", ".mkv"},
}
moved = 0
for file in downloads.iterdir():
if not file.is_file() or file.name.startswith("."):
continue
# 找出分類
category = "其他"
for cat, extensions in categories.items():
if file.suffix.lower() in extensions:
category = cat
break
# 移動到對應資料夾
target_dir = downloads / category
target_dir.mkdir(exist_ok=True)
target = target_dir / file.name
if target.exists():
# 加上時間戳避免覆蓋
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
target = target_dir / f"{file.stem}_{timestamp}{file.suffix}"
file.rename(target)
moved += 1
print(f"📁 {file.name} → {category}/")
print(f"\n✅ 共整理了 {moved} 個檔案!")
if __name__ == "__main__":
organize_downloads()
執行結果:
📁 report.pdf → 文件/
📁 screenshot.png → 圖片/
📁 data.json → 程式碼/
📁 archive.zip → 壓縮檔/
✅ 共整理了 4 個檔案!
九. os.path vs pathlib 對照表 #
如果你習慣用 os.path,這張對照表可以幫你快速切換:
| 功能 | os.path 寫法 | pathlib 寫法 |
|---|---|---|
| 串接路徑 | os.path.join(a, b) |
Path(a) / b |
| 取檔名 | os.path.basename(p) |
p.name |
| 取副檔名 | os.path.splitext(p)[1] |
p.suffix |
| 取目錄 | os.path.dirname(p) |
p.parent |
| 是否存在 | os.path.exists(p) |
p.exists() |
| 是否為檔案 | os.path.isfile(p) |
p.is_file() |
| 是否為目錄 | os.path.isdir(p) |
p.is_dir() |
| 絕對路徑 | os.path.abspath(p) |
p.resolve() |
| 展開 ~ | os.path.expanduser(p) |
p.expanduser() |
| 讀取檔案 | open(p).read() |
p.read_text() |
| 列出目錄 | os.listdir(p) |
p.iterdir() |
| 遞迴搜尋 | glob.glob("**/*", recursive=True) |
p.rglob("*") |
看出差別了嗎?pathlib 的寫法幾乎都更短、更直覺!
十. 小技巧與注意事項 #
1. 與字串的轉換 #
有些函式庫還不支援 Path 物件,需要轉回字串:
p = Path("/data/file.csv")
# Path → str
str(p) # "/data/file.csv"
# str → Path
Path("/data/file.csv")
# 大部分現代函式庫都支援 Path 物件了
import json
data = json.loads(Path("data.json").read_text())
2. 用 Type Hint 標注 #
from pathlib import Path
def process_file(input_path: Path, output_dir: Path) -> Path:
"""處理檔案並回傳輸出路徑"""
output = output_dir / f"processed_{input_path.name}"
content = input_path.read_text()
# ... 處理邏輯 ...
output.write_text(content)
return output
3. 搭配 f-string #
project = Path("my_project")
version = "1.0.0"
release_dir = project / "releases" / version
print(f"發佈目錄:{release_dir}")
# 發佈目錄:my_project/releases/1.0.0
結語 #
pathlib 是拍拍君最推薦的 Python 標準庫模組之一。它讓原本冗長又容易出錯的檔案路徑操作,變得優雅又安全。
重點回顧:
- 🛤️ 用
Path物件取代字串路徑 - ➗ 用
/運算子串接路徑,告別os.path.join - 📖 用
.read_text()/.write_text()快速讀寫 - 🔍 用
.glob()/.rglob()搜尋檔案 - 🏷️ 用
.name、.stem、.suffix取得路徑資訊
從今天開始,試著在新專案裡全面使用 pathlib 吧!一旦習慣了,你就回不去 os.path 了 😆
我們下次見!👋