Python WeakValueDictionary: ทำ cache ที่ไม่ยื้ออายุ object
cache ปกติเป็นเจ้าของค่าที่เก็บอยู่ จึงอาจทำให้ object ใหญ่มีชีวิตตลอดอายุ process แม้ส่วนอื่นเลิกใช้แล้วครับ weakref.WeakValueDictionary เก็บค่าแบบ weak reference: เมื่อไม่มี strong reference จากเจ้าของจริง entry จะหายตาม object เหมือนสมุดรายชื่อที่ลบบัตรเมื่อเจ้าของนำของออกจากคลัง
import gc
import unittest
from weakref import WeakValueDictionary
class Preview:
def __init__(self, body: bytes):
self.body = body
class WeakCacheTests(unittest.TestCase):
def test_entry_follows_real_owner(self):
cache = WeakValueDictionary()
preview = Preview(b"forest")
cache["hero"] = preview
self.assertIs(cache["hero"], preview)
del preview
gc.collect()
self.assertNotIn("hero", cache)
if __name__ == "__main__":
unittest.main()
Cache นี้รับประกันอะไร
เหมาะกับ interning, metadata หรือ wrapper ที่สร้างใหม่ได้และมี owner ภายนอกชัดเจน ถ้า cache คือเจ้าของสำเนาเพียงคนเดียว ค่าอาจหายทันทีหลังคืนจากฟังก์ชัน ทำให้ hit rate แทบเป็นศูนย์ งานที่ต้องคุมจำนวนรายการหรือต้องเก็บอย่างน้อยช่วงเวลาหนึ่งควรใช้ LRU/TTL cache ซึ่งมี policy ชัดกว่า
การเช็ก key in cache แล้วค่อยอ่านเป็นสองจังหวะมี race ได้ เพราะ object อาจถูกเก็บกวาดระหว่างกลาง ให้อ่านด้วย cache.get(key) ครั้งเดียวแล้วเก็บผลในตัวแปร local ซึ่งกลายเป็น strong reference ระหว่างใช้งาน นอกจากนี้ built-in บางชนิด เช่น list และ dict ปกติรองรับ weak reference ไม่ได้ ต้องห่อด้วย class ที่เหมาะสม และ __slots__ ต้องมี __weakref__ หากต้องการใช้กลไกนี้
อย่าใส่ callback ที่จับ object เป้าหมายกลับแบบ strong reference เพราะจะสร้างวงจรที่ขัดกับเป้าหมาย และอย่าใช้เวลาที่ entry หายเป็น business event ที่แน่นอน เนื่องจาก garbage collection ไม่ใช่ scheduler
Recap
- ใช้ weak cache เฉพาะค่าที่สร้างซ้ำได้และมี owner ภายนอก
- อ่านด้วย
get()ครั้งเดียวแล้วถือ local reference ระหว่างใช้ - ตรวจว่าชนิดค่ารองรับ weak reference
- ใช้ LRU/TTL เมื่อ retention เป็นส่วนหนึ่งของสัญญา