2026年8月31日 星期一

Python一下:用 pathlib 整理水井村USR教材與巡查檔案

《Python一下:從風土資料到智慧生活》第 16 篇

用 pathlib 整理水井村USR教材與巡查檔案

讓教材、逐字稿、照片與巡查紀錄各就各位,而且每一步都能先預覽、再執行。

CH10 資料夾與檔案pathlib安全整理
學習目標
完成本篇後,你能用 Path 建立跨平台路徑,檢查檔案與資料夾、巡覽與搜尋檔案、建立目錄、讀寫 UTF-8 文字,並設計不覆蓋原檔的安全重新命名流程。

一、為什麼推薦 pathlib?

USR專案常同時保存教案、巡查CSV、長者逐字稿、活動照片與成果報告。直接用字串拼接路徑容易漏掉斜線,也會遇到Windows與其他系統的差異。pathlib 把路徑當成物件,操作更清楚。

先備份再整理:本篇涉及建立、移動與重新命名檔案。第一次操作請使用資料副本,先執行「預覽模式」並人工核對。程式不可覆蓋同名檔,也不要直接對整顆磁碟、雲端同步根目錄或唯一一份原始訪談資料執行。

二、建立與組合路徑

from pathlib import Path

project = Path("shuijing_usr")
lesson = project / "教材" / "Python一下_16.html"

print(project)
print(lesson)
print("檔名:", lesson.name)
print("主檔名:", lesson.stem)
print("副檔名:", lesson.suffix)
print("上層:", lesson.parent)

運算子 / 會依作業系統組合路徑,不需要手動判斷斜線方向。

三、相對路徑與絕對路徑

from pathlib import Path

current = Path.cwd()
relative_path = Path("shuijing_usr") / "巡查資料"
absolute_path = relative_path.resolve()

print("目前工作目錄:", current)
print("相對路徑:", relative_path)
print("絕對路徑:", absolute_path)

相對路徑會以「目前工作目錄」為起點。Thonny、Colab與終端機的工作目錄可能不同;找不到檔案時,先印出 Path.cwd()

四、建立USR專案資料夾

from pathlib import Path

root = Path("shuijing_usr_demo")
folders = [
    root / "教材",
    root / "巡查資料",
    root / "逐字稿" / "原始檔",
    root / "逐字稿" / "確認稿",
    root / "活動照片",
]

for folder in folders:
    folder.mkdir(parents=True, exist_ok=True)
    print("已確認資料夾:", folder)

parents=True 會一併建立缺少的上層資料夾;exist_ok=True 讓既有資料夾不會造成錯誤。

五、檢查路徑是否存在

from pathlib import Path

path = Path("shuijing_usr_demo")

print("存在:", path.exists())
print("是檔案:", path.is_file())
print("是資料夾:", path.is_dir())

不要只檢查 exists();同一個名稱可能是檔案也可能是資料夾,後續操作前應確認類型。

六、列出與搜尋檔案

from pathlib import Path

root = Path("shuijing_usr_demo")

print("第一層內容:")
for path in sorted(root.iterdir()):
    print("-", path.name)

print("所有 CSV:")
for path in sorted(root.rglob("*.csv")):
    print("-", path)
方法用途
iterdir()列出目前資料夾第一層
glob("*.txt")搜尋目前層符合規則的項目
rglob("*.csv")包含所有子資料夾的遞迴搜尋

七、讀寫UTF-8文字

from pathlib import Path

note_path = Path("shuijing_usr_demo") / "教材" / "說明.txt"
content = "水井村USR教材\n主題:從風土資料到智慧生活\n"

note_path.write_text(content, encoding="utf-8")
loaded = note_path.read_text(encoding="utf-8")

print(loaded)

write_text() 預設會覆蓋既有內容,因此實際工作前要檢查目標是否存在。大量資料或逐行處理則使用 open()with

八、安全寫入:拒絕覆蓋既有檔案

from pathlib import Path


def safe_write_text(path, content):
    path = Path(path)
    if path.exists():
        raise FileExistsError(f"目標已存在:{path}")
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")
    return path


target = Path("shuijing_usr_demo") / "教材" / "新教材.txt"
try:
    created = safe_write_text(target, "教學內容\n")
except FileExistsError as error:
    print("未寫入:", error)
else:
    print("已建立:", created)

九、用檔名取得基本資訊

from pathlib import Path

files = [
    Path("2026-08-30_巡查_示範池A.csv"),
    Path("水井三寶_白馬_確認稿.txt"),
    Path("活動照片_001.JPG"),
]

for path in files:
    print(
        "名稱:", path.name,
        "|主檔名:", path.stem,
        "|副檔名:", path.suffix.lower(),
    )

使用 suffix.lower() 可同時處理 .JPG.jpg。但只看副檔名不能保證檔案內容安全或格式正確。

十、依副檔名規劃分類

from pathlib import Path

CATEGORIES = {
    ".html": "教材",
    ".pdf": "成果報告",
    ".csv": "巡查資料",
    ".txt": "逐字稿",
    ".jpg": "活動照片",
    ".jpeg": "活動照片",
    ".png": "活動照片",
}


def choose_folder(path):
    return CATEGORIES.get(path.suffix.lower(), "待確認")


for name in ["lesson.html", "reading.csv", "photo.JPG", "unknown.bin"]:
    path = Path(name)
    print(path.name, "→", choose_folder(path))

未知格式放入「待確認」,不要自動刪除。分類規則也應區分原始檔、工作檔與公開版,不能只依副檔名決定公開權限。

十一、先預覽,再重新命名

下面先建立「來源→目標」計畫,不立即變更檔案。只有確認來源存在、目標不重複且位於允許資料夾時才執行。

from pathlib import Path


def build_rename_plan(folder):
    folder = Path(folder)
    plan = []
    for number, source in enumerate(
        sorted(folder.glob("*.jpg")),
        start=1,
    ):
        target = source.with_name(f"活動照片_{number:03d}.jpg")
        plan.append((source, target))
    return plan


def show_plan(plan):
    for source, target in plan:
        print(f"預覽:{source.name} → {target.name}")


plan = build_rename_plan("shuijing_usr_demo/活動照片")
show_plan(plan)

十二、安全執行重新命名

def apply_rename_plan(plan, confirmed=False):
    if not confirmed:
        print("預覽模式:沒有變更任何檔案")
        return

    targets = [target for _, target in plan]
    if len(targets) != len(set(targets)):
        raise ValueError("計畫中有重複目標檔名")

    for source, target in plan:
        if not source.is_file():
            raise FileNotFoundError(f"來源不存在:{source}")
        if target.exists() and target != source:
            raise FileExistsError(f"目標已存在:{target}")

    for source, target in plan:
        if source != target:
            source.rename(target)
            print(f"已重新命名:{source.name} → {target.name}")


apply_rename_plan(plan, confirmed=False)
為何預設不執行?重新命名與移動可能破壞教材連結、相簿引用及研究資料對照關係。應先輸出計畫、建立備份、人工確認,再把 confirmed 改為 True

十三、建立檔案盤點報告

from collections import Counter
from pathlib import Path


def inventory(folder):
    folder = Path(folder)
    files = [path for path in folder.rglob("*") if path.is_file()]
    extensions = Counter(
        path.suffix.lower() or "(無副檔名)" for path in files
    )
    return files, extensions


files, extensions = inventory("shuijing_usr_demo")
print("檔案總數:", len(files))
for extension, count in sorted(extensions.items()):
    print(extension, count)

十四、常見錯誤檢查表

現象可能原因修正方式
找不到明明存在的檔案工作目錄不同先檢查 Path.cwd()
文字檔出現亂碼讀寫編碼不一致確認來源並指定 UTF-8
同名檔被覆蓋寫入前未檢查使用 exists() 或排他建立模式
搜尋不到大寫.JPG副檔名大小寫不同取 suffix.lower() 後判斷
整理後連結失效外部文件仍引用舊路徑先盤點依賴並輸出更名對照表

十五、USR實作挑戰

挑戰A|教材盤點
統計HTML、PDF、圖片及程式檔數量,輸出副檔名報告與「待確認」清單,不移動任何檔案。
挑戰B|逐字稿版本管理
規劃「原始檔、整理稿、講述者確認稿、公開版」資料夾,為每個版本設計清楚檔名,保留授權資訊。
挑戰C|安全照片更名
只建立照片更名預覽與CSV對照表,人工確認沒有同名、錯序或連結依賴後才執行。

十六、與生成式AI協作

請擔任Python pathlib與資料治理助教。
我要整理USR教材、逐字稿、巡查CSV及活動照片。
請設計一個安全整理程式:
1. 第一次執行只能預覽,不可移動或刪除;
2. 不得覆蓋既有檔案;
3. 未知格式放入待確認;
4. 保留原始逐字稿與授權資料;
5. 執行前檢查重複目標與遺失來源;
6. 輸出來源、目標及結果對照表。
請先說明風險,再提供可測試的函式。
本篇小結
pathlib讓路徑操作跨平台且容易閱讀。真正可靠的檔案整理還需要三道防線:使用副本、先預覽計畫、拒絕覆蓋既有資料。

沒有留言:

張貼留言