งูสีมิ้นต์ต้นฉบับเดินตามทางสีครีมผ่านต้นไม้แตกแขนง โดยตัดกิ่งสีอำพันที่ไม่ต้องสำรวจ

Python Path.walk: เดินโฟลเดอร์แบบ prune ได้และไม่ตาม symlink โดยปริยาย

น้องกายEngineering

Path.rglob() เหมาะกับการหา pattern เดียว แต่เมื่อเราต้องข้าม .cache, ควบคุม error หรือประมวลผลทีละ directory, Path.walk() ให้ control แบบ os.walk() พร้อมคืน Path สำหรับ directory ปัจจุบัน

WHY: pruning ลดทั้งเวลาและความเสี่ยง

ในโหมด top_down=True เราแก้รายการ dirnames ในที่เดิมเพื่อตัด subtree ได้ ถ้าไม่ทำ โปรแกรมอาจเดินเข้า build cache, virtual environment หรือ mount ที่ใหญ่กว่างานจริงมาก การ prune ก่อน descent เร็วกว่าค้นพบไฟล์แล้วทิ้งภายหลัง

ค่าเริ่มต้น follow_symlinks=False ไม่ตาม symlink directory และรายงานมันใน filenames พฤติกรรมนี้ช่วยลดวงวนโดยไม่ตั้งใจ แต่ไม่ใช่ sandbox: ระหว่าง scan path อาจถูกสลับได้ และไฟล์ที่พบยังต้องผ่าน authorization ก่อนเปิด

HOW: ค้น Markdown โดยข้าม cache

import tempfile
import unittest
from pathlib import Path


def markdown_files(root):
    found = []
    for directory, dirnames, filenames in root.walk(top_down=True):
        dirnames[:] = [name for name in dirnames if name != ".cache"]
        found.extend(
            directory / name
            for name in filenames
            if name.endswith(".md")
        )
    return sorted(path.relative_to(root) for path in found)


class PathWalkTests(unittest.TestCase):
    def test_prunes_cache_and_finds_nested_markdown(self):
        with tempfile.TemporaryDirectory() as temp:
            root = Path(temp)
            (root / "docs").mkdir()
            (root / ".cache").mkdir()
            (root / "README.md").write_text("root")
            (root / "docs" / "guide.md").write_text("guide")
            (root / "docs" / "image.png").write_bytes(b"png")
            (root / ".cache" / "stale.md").write_text("skip")

            self.assertEqual(
                markdown_files(root),
                [Path("README.md"), Path("docs/guide.md")],
            )

    def test_empty_tree_returns_empty_list(self):
        with tempfile.TemporaryDirectory() as temp:
            self.assertEqual(markdown_files(Path(temp)), [])


if __name__ == "__main__":
    unittest.main()

โดยปริยาย error จาก os.scandir() ถูกละไว้ หากความครบถ้วนสำคัญ ให้ส่ง on_error ที่ log หรือ raise พร้อม path มิฉะนั้นรายงานอาจดูสำเร็จทั้งที่อ่านบาง directory ไม่ได้ และถ้าเปิด follow_symlinks=True ต้องมีวงจำกัดหรือชุด inode ที่เคยเห็น เพราะ Path.walk() ไม่ติดตามวงวนให้

ระวังความต่างจาก os.walk(): symlink ที่ชี้ directory จะอยู่ใน filenames เมื่อไม่ตาม link ถ้า logic ลบไฟล์จากรายการนั้น อย่าลบ symlink target โดยเผลอ resolve ก่อนตรวจขอบเขต

Recap

ใช้ Path.walk() เมื่อต้อง prune หรือกำหนด error policy แก้ dirnames[:] ในโหมด top-down และคง follow_symlinks=False จนมีเหตุผลชัด งานลองวันนี้คือเพิ่ม test ที่มี cache directory และ permission error ให้เครื่องมือสแกนไฟล์ของทีมครับ