งูต้นฉบับกำลังวางถาดใบเล็กซ้อนเหนือกล่องตั้งค่าหลายชั้นบนโต๊ะสีมิ้นต์

Python ChainMap.new_child: ซ้อน scope ใหม่โดยไม่ copy config ทั้งก้อน

พี่โชว์Engineering

config หลายระดับมักมี default, environment และ override ต่อคำขอ หาก merge ด้วย dict ทุกครั้ง เรา copy ค่าและอาจลืมว่าค่าไหนมาจากชั้นใดครับ ChainMap.new_child() เพิ่ม mapping หน้า chain ทำให้ lookup เห็น override ก่อน แต่ยังย้อนกลับไป parent ได้

WHY: ถาดงานวันนี้วางบนตู้เอกสารเดิม

เหมือนวางถาดเอกสารชั่วคราวบนตู้หลัก เวลาหาเอกสารเราดูถาดบนก่อน ถ้าไม่มีก็ค่อยเปิดลิ้นชักเดิม เมื่อจบงานยกถาดออกด้วย parents ได้โดยไม่คัดลอกทั้งตู้

from collections import ChainMap
import unittest


class ChainMapScopeTests(unittest.TestCase):
    def test_new_child_overrides_without_mutating_parent(self):
        defaults = {"timeout": 30, "region": "th"}
        base = ChainMap(defaults)
        request = base.new_child({"timeout": 5})

        self.assertEqual(request["timeout"], 5)
        self.assertEqual(request["region"], "th")
        request["trace_id"] = "trace-1"
        self.assertNotIn("trace_id", defaults)
        self.assertIs(request.parents.maps[0], defaults)

    def test_empty_child_accepts_local_writes(self):
        base = ChainMap({"mode": "safe"})
        child = base.new_child()
        child["mode"] = "strict"
        self.assertEqual(child["mode"], "strict")
        self.assertEqual(base["mode"], "safe")


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

เขียนลงชั้นหน้าเสมอ

การ assign/deletion ปกติกระทบ mapping แรก ไม่ได้ค้นแล้วแก้ parent ซึ่งเป็น behavior ที่ช่วยรักษา default แต่ต้องอธิบายให้ทีมเข้าใจ หากต้อง deep write ควรสร้าง abstraction ต่างหากแทนการเดา

ChainMap เป็น view สด การแก้ mapping ต้นทางจะสะท้อนใน lookup ภายหลัง จึงไม่ใช่ snapshot สำหรับ audit หรือ async job ที่ต้อง reproducible ให้ materialize เป็น dict(chain) ที่ boundary และ redact secret ก่อน log

Recap

  • ใช้ new_child สำหรับ scope/override ชั่วคราว
  • เขียนลง mapping แรกโดยตั้งใจ
  • ใช้ parents เพื่อกลับ scope เดิม
  • สร้าง snapshot เมื่อความทำซ้ำได้สำคัญกว่า live view