โฟลเดอร์สองใบบนโต๊ะมิ้นต์มีลูกศรอำพันเชื่อม และนากกายยืนข้ามระหว่างโฟลเดอร์

contextlib.chdir: เปลี่ยนโฟลเดอร์ชั่วคราวแบบปลอดภัยใน with

น้องกายEngineering

เคยเขียนสคริปต์ที่ os.chdir เข้าไปทำงานในโฟลเดอร์ชั่วคราว แล้วลืมเปลี่ยนกลับไหม ผมเคยจนพาเทสต์อื่นพังตาม

ตั้งแต่ Python 3.11 มี contextlib.chdir เป็น context manager ที่เปลี่ยน working directory ตอนเข้า with แล้วคืนค่าเดิมตอนออก แม้มี exception

ทำไมไม่ใช้ os.chdir อย่างเดียว

os.chdir เปลี่ยนสถานะโปรเซสทั้งก้อน ถ้าลืมคืนค่า หรือมีหลายเธรดแย่งกัน งานอื่นที่พึ่ง path สัมพัทธ์จะเพี้ยน chdir ใน contextlib อย่างน้อยช่วยให้ขอบเขตชัดในโค้ดแบบซิงเกิลเธรด

เอกสารเตือนว่าการเปลี่ยน cwd ทั้งโปรเซสยังไม่เหมาะกับ multithreaded อยู่ดี — ถ้าทำขนานจริง ควรส่ง path สัมบูรณ์เข้าฟังก์ชันแทน

from __future__ import annotations

import os
import tempfile
import unittest
from contextlib import chdir
from pathlib import Path


class ChdirTests(unittest.TestCase):
    def test_restores_cwd(self):
        original = Path.cwd()
        with tempfile.TemporaryDirectory() as tmp:
            target = Path(tmp).resolve()
            with chdir(target):
                self.assertEqual(Path.cwd(), target)
            self.assertEqual(Path.cwd(), original)

    def test_restores_after_error(self):
        original = Path.cwd()
        with tempfile.TemporaryDirectory() as tmp:
            target = Path(tmp).resolve()
            with self.assertRaises(RuntimeError):
                with chdir(target):
                    self.assertEqual(Path.cwd(), target)
                    raise RuntimeError("boom")
            self.assertEqual(Path.cwd(), original)


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

Recap

  • ใช้ with chdir(path): เมื่อต้องรันเครื่องมือที่อ่าน cwd
  • อย่าพึ่ง chdir ในโค้ด multithreaded — ส่ง absolute path แทน
  • ทดสอบว่าออกจาก with แล้ว cwd กลับจริง รวมกรณีมี exception