Skip to content

บทที่ 5.3: Context Managers (การจัดการ Resource ด้วยคำสั่ง with)

เอกสารนี้อธิบายเกี่ยวกับแนวคิดของ Context Manager การใช้งานคำสั่ง with เพื่อจัดการทรัพยากร (Resource Management) รวมถึงการสร้าง Context Manager ทั้งในรูปแบบ Class (__enter__, __exit__) และการใช้ @contextmanager decorator จากโมดูล contextlib


1. แนวคิดของ Context Manager และคำสั่ง with

การจัดการทรัพยากรระบบ เช่น การเปิด/ปิดไฟล์ การเชื่อมต่อฐานข้อมูล หรือการจับ Lock ใน Threading หากโปรแกรมเกิดข้อผิดพลาดขึ้นก่อนที่จะสั่งปิด อาจทำให้เกิดทรัพยากรรั่วไหล (Resource Leak)

คำสั่ง with ช่วยควบคุมการเปิดและคืนทรัพยากรโดยอัตโนมัติอย่างปลอดภัย แม้ว่าจะเกิด Exception ขึ้นในระหว่างการทำงานก็ตาม

# การเขียนแบบเดิม (เสี่ยงทรัพยากรรั่วไหลหากเกิด Error)
file = open("data.txt", "w")
try:
    file.write("Hello")
finally:
    file.close()

# การใช้ with statement (จัดการปิดไฟล์ให้อัตโนมัติ)
with open("data.txt", "w") as file:
    file.write("Hello")

2. การสร้าง Context Manager ด้วย Class

เราสามารถสร้าง Context Manager ขึ้นมาเองได้โดยการนิยาม Dunder Methods 2 ตัวใน Class: * __enter__(): ทำงานเมื่อเริ่มต้นเข้าสู่คำสั่ง with (คืนค่า Object ที่ต้องการอ้างอิงผ่าน as) * __exit__(): ทำงานเมื่อออกจากคำสั่ง with (รับ Parameter เกี่ยวกับ Exception หากมีข้อผิดพลาดเกิดขึ้น)

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        print(f"กำลังเปิดไฟล์: {self.filename}")
        self.file = open(self.filename, self.mode, encoding="utf-8")
        return self.file  # ค่าที่จะถูกส่งไปที่ตัวแปรหลังคำสั่ง as

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"กำลังปิดไฟล์: {self.filename}")
        if self.file:
            self.file.close()
        # คืนค่า True หากต้องการดักจับ exception ไม่ให้พุ่งออกไปภายนอก
        return False

# การใช้งาน
with FileManager("test.txt", "w") as f:
    f.write("เขียนข้อมูลทดสอบ")
# Output:
# กำลังเปิดไฟล์: test.txt
# กำลังปิดไฟล์: test.txt

3. การสร้าง Context Manager ด้วย contextlib

โมดูล contextlib มี Decorator ชื่อ @contextmanager ที่ช่วยให้เราสร้าง Context Manager ผ่าน Generator Function ร่วมกับคำสั่ง yield ได้ง่ายขึ้น โดยไม่ต้องเขียนเป็น Class

from contextlib import contextmanager
import time

@contextmanager
def timer(label):
    start_time = time.time()
    try:
        # ส่ง Control กลับไปให้บล็อกโค้ดภายใน with
        yield
    finally:
        # ทำงานเสมอเมื่อออกจากบล็อก with
        elapsed_time = time.time() - start_time
        print(f"[{label}] ใช้เวลาทำงาน: {elapsed_time:.4f} วินาที")

# การใช้งาน
with timer("คำนวณวนซ้ำ"):
    total = sum(i ** 2 for i in range(1000000))
# Output: [คำนวณวนซ้ำ] ใช้เวลาทำงาน: 0.0512 วินาที (โดยประมาณ)

4. ตัวอย่างการประยุกต์ใช้งานจริง (Change Working Directory)

ตัวอย่างการเปลี่ยน Working Directory ชั่วคราวภายในบล็อก with แล้วสลับกลับคืนตำแหน่งเดิมเมื่อทำงานเสร็จ

import os
from contextlib import contextmanager

@contextmanager
def change_dir(destination):
    origin = os.getcwd()
    try:
        os.chdir(destination)
        yield
    finally:
        os.chdir(origin)

# การใช้งาน
print("ตำแหน่งปัจจุบัน:", os.getcwd())
# with change_dir("/tmp"):
#     print("ตำแหน่งใน with:", os.getcwd())
# print("ตำแหน่งหลังออกจาก with:", os.getcwd())