On this page
Context Manager (with)
ใช้และเขียน with เพื่อจัดการ resource (ไฟล์, connection) ให้เปิด-ปิดถูกต้องเสมอ แม้เกิด error
คุณเคยใช้ with open(...) เปิดไฟล์มาแล้ว หัวข้อนี้อธิบายว่ามันทำงานอย่างไร ทำไมสำคัญ และวิธีเขียน context manager ของตัวเอง — เครื่องมือที่การันตีว่า resource ถูกปิดเสมอ ป้องกัน memory/handle leak
ทำไมต้อง with
ถ้าเปิดไฟล์เองแล้วเกิด error ก่อนถึง close() ไฟล์จะค้างเปิด with แก้ปัญหานี้โดยปิดให้อัตโนมัติเมื่อออกจากบล็อก ไม่ว่าจะจบปกติหรือเกิด exception
# ❌ เสี่ยง: ถ้า error ก่อน close() ไฟล์ค้างเปิด
f = open("data.txt", encoding="utf-8")
data = f.read()
f.close()
# ✅ with: ปิดให้อัตโนมัติเสมอ แม้เกิด error ในบล็อก
with open("data.txt", encoding="utf-8") as f:
data = f.read()
# ออกจากบล็อก = ไฟล์ถูกปิดแล้วแน่นอนเขียน context manager ด้วย class (__enter__/__exit__)
object จะใช้กับ with ได้ ต้องมี 2 method: __enter__ (ทำตอนเข้าบล็อก, ค่าที่ return ไปเป็น as) และ __exit__ (ทำตอนออก แม้มี error)
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self # ค่าที่ as จะได้
def __exit__(self, exc_type, exc_val, exc_tb):
import time
elapsed = time.perf_counter() - self.start
print(f"ใช้เวลา {elapsed:.4f} วินาที")
# return False = ถ้ามี exception ให้โยนต่อ (ปกติ)
with Timer():
total = sum(range(1000000))
# ใช้เวลา 0.0xx วินาที (พิมพ์ตอนออกจากบล็อกเสมอ)พารามิเตอร์ exc_type, exc_val, exc_tb จะมีค่าถ้าในบล็อกเกิด exception ทำให้ context manager เป็นที่เหมาะสำหรับ "คืน resource" (ปิดไฟล์, ปิด connection, ปลด lock) ที่ต้องเกิดขึ้นไม่ว่าอะไรจะพัง
วิธีลัด: contextlib.contextmanager
เขียน class เต็มยุ่งยาก ใช้ decorator @contextmanager กับ generator แทนได้ โค้ดก่อน yield = __enter__, หลัง yield = __exit__
from contextlib import contextmanager
import time
@contextmanager
def timer():
start = time.perf_counter()
try:
yield # ตรงนี้คือ "ในบล็อก with"
finally:
elapsed = time.perf_counter() - start
print(f"ใช้เวลา {elapsed:.4f} วินาที")
with timer():
total = sum(range(1000000))ใส่ส่วนคืน resource ไว้ใน finally เพื่อให้ทำงานแม้บล็อกเกิด error — ถ้าไม่ใส่ finally แล้วโค้ดในบล็อก raise การคืน resource จะถูกข้าม กลายเป็น leak
สรุปหัวข้อนี้
- with การันตีว่า resource ถูกคืน/ปิดเสมอ แม้เกิด error
- class context manager ต้องมี __enter__ และ __exit__
- ค่าที่ __enter__ คืน = ตัวที่รับด้วย as
- วิธีลัด: @contextmanager กับ generator (try/finally + yield)
1) เขียน context manager ด้วย class ที่ print "เข้า" ตอนเริ่มและ "ออก" ตอนจบ 2) เขียน timer ด้วย @contextmanager 3) ลองให้เกิด error ในบล็อก with แล้วสังเกตว่า __exit__/finally ยังทำงาน 4) เขียน context manager ที่เปิด-ปิดไฟล์ (จำลอง) พร้อม print สถานะ