บทที่ 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())