Python Path.walk: เดินโฟลเดอร์แบบ prune ได้และไม่ตาม symlink โดยปริยาย
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 และ symlink policy ต้องเขียนให้เห็น
โดยปริยาย 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 ให้เครื่องมือสแกนไฟล์ของทีมครับ