งูต้นฉบับเลื่อนลูกปัดเหตุการณ์ผ่านรางห้าช่อง โดยลูกปัดเก่าสุดตกลงถาดเมื่อของใหม่เข้ามา

Python deque(maxlen): เก็บประวัติล่าสุดโดยไม่ปล่อยหน่วยความจำโตไม่จบ

พี่โชว์Engineering

รายการ log ล่าสุด, ค่า latency รอบสั้น หรือคำสั่ง undo มักต้องเก็บเพียง N รายการ การใช้ list แล้วคอยเช็กความยาวและ pop(0) ทำให้กฎขอบเขตกระจายอยู่หลายจุด และการลบหัว list ต้องเลื่อนสมาชิกที่เหลือ collections.deque ที่กำหนด maxlen จะทิ้งสมาชิกฝั่งตรงข้ามให้อัตโนมัติเมื่อเต็มครับ

from collections import deque
import unittest


class RecentEvents:
    def __init__(self, capacity: int) -> None:
        if capacity <= 0:
            raise ValueError("capacity must be positive")
        self._items: deque[str] = deque(maxlen=capacity)

    def record(self, event: str) -> None:
        self._items.append(event)

    def snapshot(self) -> tuple[str, ...]:
        return tuple(self._items)


class RecentEventsTests(unittest.TestCase):
    def test_discards_oldest_when_full(self):
        history = RecentEvents(3)
        for event in ("open", "edit", "save", "close"):
            history.record(event)
        self.assertEqual(history.snapshot(), ("edit", "save", "close"))

    def test_rejects_zero_capacity(self):
        with self.assertRaises(ValueError):
            RecentEvents(0)


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

maxlen เป็น immutable หลังสร้าง ถ้าต้องเปลี่ยนขนาดให้สร้าง deque ใหม่จากข้อมูลเดิม และต้องตกลงให้ชัดว่าเมื่อเต็มจะทิ้งข้อมูลเงียบ ๆ ได้จริงหรือไม่ สำหรับ audit log หรือธุรกรรม การทิ้งอัตโนมัติเป็นพฤติกรรมผิด ควรส่งไป durable storage และให้ backpressure แทน

การอ่าน index ตรงกลาง deque ไม่ใช่จุดแข็ง เพราะจะช้าลงเมื่อไกลจากปลายทั้งสอง ถ้างานเน้น random access ใช้ list ยังเหมาะกว่า แต่ถ้าเป็น rolling window ที่เพิ่มและนำออกจากหัวท้าย deque(maxlen=N) ทำให้ memory bound เป็นส่วนหนึ่งของโครงสร้าง ไม่ใช่คำสัญญาใน comment

Recap

  • ใช้ maxlen เมื่อยอมทิ้งรายการเก่าสุดได้
  • validate capacity ก่อนสร้าง
  • คืน snapshot แทนเปิด deque ภายในให้แก้ตรง ๆ
  • อย่าใช้ rolling buffer แทน audit log ถาวร