งูสีเขียวต้นฉบับต่อรางสองเส้นให้ผ่านประตูเดียวกัน โดยรางหนึ่งมีตู้ทรัพยากรและอีกรางว่าง

Python nullcontext: ทำให้เส้นทางมีและไม่มี context manager ใช้โครงเดียวกัน

พี่โชว์Engineering

ฟังก์ชันบางตัวรับ resource ที่เปิดมาแล้ว หรือเปิด default เองเมื่อ caller ไม่ส่งมา ถ้าเขียน if แยกสองเส้น เรามักทำ validation หรือ cleanup ไม่เท่ากัน contextlib.nullcontext คือ context manager ที่ไม่ทำ cleanup และคืนค่าที่ให้ไว้ ทำให้ทั้งสองกรณีไหลผ่าน with เดียวกันครับ

import io
import unittest
from contextlib import nullcontext


def first_line(stream=None):
    manager = nullcontext(stream) if stream is not None else io.StringIO("default\n")
    with manager as source:
        return source.readline().strip()


class NullContextTests(unittest.TestCase):
    def test_uses_caller_owned_stream_without_closing_it(self):
        stream = io.StringIO("custom\n")
        self.assertEqual(first_line(stream), "custom")
        self.assertFalse(stream.closed)

    def test_creates_default_stream(self):
        self.assertEqual(first_line(), "default")


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

สัญญา ownership ต้องชัด: ตัวอย่างไม่ปิด stream ของ caller เพราะ nullcontext ไม่ทำอะไร แต่ resource ที่ฟังก์ชันสร้างเองควรใช้ context manager ที่ปิดจริง อย่าใช้ nullcontext กลบความไม่แน่ใจว่าใครต้องคืนทรัพยากร และถ้าเป็น async ให้ใช้รูปแบบ async ของ nullcontext กับ async with ตามชนิดที่รับมา

Recap

  • ใช้เมื่อมีทางเลือก “caller ส่ง resource” กับ “สร้างเอง”
  • เขียน ownership contract ว่าใครปิดของชิ้นไหน
  • อย่าใช้แทน cleanup ที่หายไป
  • ทดสอบว่าของ caller ยังเปิดอยู่หลังฟังก์ชันจบ