Python weakref.finalize: วาง cleanup สำรองโดยไม่ทำให้ object อยู่ค้าง
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 ทำงาน