งูต้นฉบับครอบหยดน้ำเล็กหนึ่งหยดด้วยถ้วยแก้ว ขณะที่สัญญาณผิดปกติสีอำพันยังผ่านออกด้านข้าง

Python contextlib.suppress: กลืนเฉพาะ error ที่ขั้นตอนนั้นยอมรับได้

พี่โชว์Engineering

งาน cleanup แบบ idempotent มักยอมรับได้ถ้าไฟล์หายไปแล้ว แต่ไม่ควรกลืน permission error หรือ disk failure การเขียน try/except Exception: pass ทำให้ระบบดูเหมือนสำเร็จทั้งที่มีปัญหาจริง contextlib.suppress ทำให้เราเขียนขอบเขตสั้น ๆ และระบุ exception ที่ตั้งใจเพิกเฉยได้ตรงจุดครับ

from collections.abc import Callable
from contextlib import suppress
import unittest


def remove_if_present(remove: Callable[[], None]) -> None:
    with suppress(FileNotFoundError):
        remove()


class SuppressTests(unittest.TestCase):
    def test_missing_resource_is_already_clean(self):
        def missing() -> None:
            raise FileNotFoundError("gone")

        remove_if_present(missing)

    def test_permission_problem_is_not_hidden(self):
        def denied() -> None:
            raise PermissionError("read-only")

        with self.assertRaises(PermissionError):
            remove_if_present(denied)


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

ชื่อ exception คือ policy ของฟังก์ชัน: FileNotFoundError หมายถึง “ปลายทางที่ไม่มีถือว่าสะอาดแล้ว” แต่ PermissionError หมายถึง “เรายังไม่รู้ว่า cleanup สำเร็จหรือไม่” จึงต้องส่งต่อให้ผู้เรียกตัดสินใจ การครอบโค้ดยาวหลายบรรทัดทำให้ไม่ชัดว่า exception มาจากคำสั่งใด ควรจำกัด with suppress(...) ให้เล็กที่สุด

ถ้าต้องบันทึก warning, เพิ่ม metric หรือคืนสถานะว่าเกิดอะไรขึ้น ใช้ try/except ปกติจะชัดกว่า suppress และหาก exception บ่งชี้ bug เช่น TypeError อย่าเพิ่มเข้า list เพียงเพื่อให้ job ผ่าน การกลืน error ควรเป็นผลจากสัญญาของ operation ไม่ใช่ทางลัดลดเสียง log

Recap

  • suppress เฉพาะ exception ที่เท่ากับสถานะสำเร็จของ operation
  • ครอบ statement ให้น้อยที่สุด
  • เทสต์ว่า exception อื่นยัง propagate
  • ใช้ try/except เมื่อจำเป็นต้อง log หรือคืนสถานะ