งูต้นฉบับผูกเชือกสำรองจากกล่องทรัพยากรไปยังคันปล่อยอัตโนมัติบนโต๊ะสว่าง

Python weakref.finalize: วาง cleanup สำรองโดยไม่ทำให้ object อยู่ค้าง

พี่โชว์Engineering

resource wrapper ที่ caller ลืมปิดอาจทิ้งไฟล์ชั่วคราวหรือ handle ไว้จน process จบครับ weakref.finalize ลงทะเบียน callback ที่ทำงานเมื่อ owner ถูกเก็บ โดย finalizer ไม่ถือ strong reference กลับไปหา owner เอง

WHY: เข็มขัดนิรภัย ไม่ใช่พวงมาลัย

เส้นทางหลักควรเป็น with หรือ close() ที่เรียกตรงเวลาเสมอ Finalizer เหมือนเข็มขัดนิรภัยสำหรับกรณี caller พลาด ไม่ใช่วิธีควบคุมจังหวะ cleanup เพราะ garbage collection ไม่รับประกันว่าจะเกิดทันที และตอน interpreter shutdown dependency บางอย่างอาจไม่พร้อมแล้ว

import unittest
import weakref


class Lease:
    def __init__(self, events):
        self._finalizer = weakref.finalize(self, events.append, "released")

    def close(self):
        self._finalizer()

    @property
    def alive(self):
        return self._finalizer.alive


class FinalizeTests(unittest.TestCase):
    def test_explicit_close_runs_once(self):
        events = []
        lease = Lease(events)
        self.assertTrue(lease.alive)
        lease.close()
        lease.close()
        self.assertEqual(events, ["released"])
        self.assertFalse(lease.alive)

    def test_detach_cancels_automatic_cleanup(self):
        events = []
        lease = Lease(events)
        detached = lease._finalizer.detach()
        self.assertIsNotNone(detached)
        self.assertEqual(events, [])
        self.assertFalse(lease.alive)


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

ระวัง callback จับ owner กลับมา

หาก callback เป็น bound method ของ object เดิม มันอาจถือ self เป็น strong reference แล้วทำให้ object ไม่ถูกเก็บ ใช้ฟังก์ชันภายนอกพร้อม argument ที่จำเป็นเท่านั้น และอย่าให้ callback พึ่ง global ที่อาจถูกทำลายตอน shutdown

cleanup ควร idempotent และไม่โยน exception ที่ทำให้ข้อมูลวินิจฉัยหาย ถ้า resource สำคัญ เช่น transaction, lock ข้าม process หรือไฟล์ที่ต้อง fsync ให้ใช้ lifecycle ชัดเจนและถือการพึ่ง finalizer เป็น bug signal ที่ควรมี metric

Recap

  • ใช้ with/close เป็นเส้นทางหลัก
  • ให้ finalizer เป็น safety net และทำงานซ้ำได้อย่างปลอดภัย
  • อย่าให้ callback จับ owner ผ่าน bound method
  • อย่าผูก correctness กับเวลาที่ garbage collector ทำงาน