ข้ามไปยังเนื้อหา

Context Managers และ with

ใน TypeScript ถ้าอยากมั่นใจว่า resource ได้คืนแน่นอนไม่ว่าจะเกิด exception หรือไม่ คุณต้องเขียนบล็อก try/finally เอง

Python ใช้แนวคิดเดียวกัน แต่ยกขึ้นมาเป็น syntax ระดับภาษาผ่าน with statement ตัว with การันตีว่า resource setup และ teardown ครบถ้วน ต่อให้โค้ดข้างในจะ raise exception ก็ตาม โดยที่คุณไม่ต้องมานั่งเขียน try/finally ซ้ำ ๆ ทุกครั้ง

การเปิดไฟล์คือตัวอย่างที่พบบ่อยที่สุด TypeScript ไม่มี file API ระดับภาษาในตัว แต่ pattern จะมีลักษณะดังนี้:

TypeScript
// TypeScript: manual try/finally for resource cleanup
import * as fs from "fs";
function readFile(path: string): string {
const fd = fs.openSync(path, "r");
try {
const buf = Buffer.alloc(1024);
const bytesRead = fs.readSync(fd, buf, 0, 1024, 0);
return buf.toString("utf8", 0, bytesRead);
} finally {
fs.closeSync(fd); // always runs
}
}
Python
# Python: with statement handles open/close automatically
def read_file(path):
with open(path, "r") as f:
return f.read()
# f.close() is called automatically — even if read() raises

บล็อก with เรียก f.close() โดยอัตโนมัติเมื่อออกจากบล็อก ไม่ว่าจะออกปกติหรือเพราะ exception ไม่ต้องมี try/finally ที่ซ้ำซ้อน

object ใดก็ตามที่ implement เมธอด __enter__ และ __exit__ สามารถใช้กับ with statement ได้ นี่คือ context manager protocol

TypeScript
// TypeScript: class-based resource management
class ManagedResource {
constructor(private name: string) {}
acquire(): this {
console.log(`Acquiring ${this.name}`);
return this;
}
release(): void {
console.log(`Releasing ${this.name}`);
}
use<T>(fn: (r: this) => T): T {
this.acquire();
try {
return fn(this);
} finally {
this.release();
}
}
}
new ManagedResource("database connection").use(resource => {
console.log(`Using ${resource}`);
});
Python
# Python: __enter__ and __exit__ make any class a context manager
class ManagedResource:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"Acquiring {self.name}")
return self # bound to the 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Releasing {self.name}")
return False # False = don't suppress exceptions
with ManagedResource("database connection") as resource:
print(f"Using ${resource.name}")

__exit__ รับ argument สามตัวที่อธิบาย exception ที่เกิดขึ้น (ถ้ามี) คือ type, value และ traceback ถ้าคืน True Python จะกลืน exception นั้นทิ้ง แต่ถ้าคืน False หรือ None exception จะเด้งต่อออกไปตามปกติ

การเขียน class เต็มรูปแบบเพียงเพื่อจัดการ setup/teardown เล็กน้อยอาจดูมากเกินไป @contextmanager decorator จาก contextlib ให้คุณเขียน generator ธรรมดาแทนได้ โค้ดก่อน yield คือ __enter__ โค้ดหลัง yield คือ __exit__

TypeScript
// TypeScript: helper function simulating @contextmanager
function withTimer<T>(label: string, fn: () => T): T {
console.log(`[${label}] starting`);
try {
return fn();
} finally {
console.log(`[${label}] done`);
}
}
const result = withTimer("my block", () => {
let total = 0;
for (let i = 0; i < 1000; i++) total += i;
console.log(`sum = ${total}`);
return total;
});
Python
# Python: @contextmanager turns a generator into a context manager
from contextlib import contextmanager
@contextmanager
def timer(label):
print(f"[{label}] starting")
yield # control passes to the 'with' block here
print(f"[{label}] done")
with timer("my block"):
total = sum(range(1000))
print(f"sum = ${total}")
from contextlib import contextmanager
class ManagedResource:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"Acquiring {self.name}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Releasing {self.name}")
return False
with ManagedResource("database connection") as resource:
print(f"Using {resource.name}")
@contextmanager
def timer(label):
print(f"[{label}] starting")
yield
print(f"[{label}] done")
with timer("my block"):
total = sum(range(1000))
print(f"sum = {total}")
เมธอดใดถูกเรียกเมื่อเข้าสู่บล็อก with?
__exit__ คืนค่า True หมายความว่าอะไร?
ใน @contextmanager decorator โค้ดหลัง yield ทำงานเมื่อไหร่?
วิธีใดถูกต้องในการใช้ context manager สองตัวพร้อมกัน?