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

Context Managers และ with

TypeScript ไม่มี keyword with เมื่อต้องการยึด resource แล้วการันตีว่ามันถูกคืนแน่นอน ไม่ว่าจะเป็น file handle, database connection, หรือ lock คุณต้องเขียน try/finally เอง ตั้ง resource ก่อน try แล้วเก็บกวาดใน finally วิธีนี้ใช้ได้ แต่ต้องจำ pattern และเขียนให้ถูกต้องทุกครั้ง

Python’s with statement ห่อหุ้ม setup และ teardown ไว้ในตัว object เอง object กำหนดว่าจะเกิดอะไรตอนเข้าและตอนออก และ Python การันตีว่า exit จะทำงาน — แม้จะมี exception เกิดขึ้นในบล็อกก็ตาม logic การเก็บกวาดอยู่ที่ resource ไม่กระจัดกระจายไปทุกจุดที่เรียกใช้

การเปิดไฟล์คือตัวอย่างที่พบบ่อยที่สุด 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 ที่ซ้ำซ้อน

class จะกลายเป็น context manager ได้โดย implement เมธอด dunder สองตัว: __enter__ และ __exit__ Python เรียก __enter__ ตอนเข้าบล็อก with และเรียก __exit__ ตอนออก — ไม่ว่าจะออกด้วยเหตุผลใดก็ตาม

TypeScript
// TypeScript: try/finally for guaranteed cleanup
class ManagedResource {
constructor(private name: string) {}
acquire(): this {
console.log(`Acquiring ${this.name}`);
return this;
}
release(): void {
console.log(`Releasing ${this.name}`);
}
use(): void {
console.log(`Using ${this.name}`);
}
}
const resource = new ManagedResource("database connection");
resource.acquire();
try {
resource.use();
} finally {
resource.release(); // must remember to call this
}
Python
# Python: with statement handles acquire and release automatically
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 # do not suppress exceptions
with ManagedResource("database connection") as resource:
print(f"Using {resource.name}")
# release is guaranteed even if an exception occurs

ค่าที่ __enter__ return จะถูก bind เข้ากับตัวแปรหลัง as ส่วน as clause นั้นไม่บังคับ — เขียนแค่ with ManagedResource("x"): ก็ได้ถ้าไม่ต้องการอ้างอิงถึง resource ในบล็อก

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

TypeScript
// TypeScript: a utility wrapper function with try/finally
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
from contextlib import contextmanager
@contextmanager
def timer(label):
print(f"[{label}] starting")
yield # execution of the with block happens 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}")
class ต้อง implement เมธอด dunder สองตัวใดเพื่อใช้เป็น context manager ได้?
ค่าใดถูก bind เข้ากับตัวแปรหลัง "as" ใน with statement?
ใน @contextmanager function บล็อก with จะทำงานตอนไหน?
__exit__ กลืน exception ที่เกิดในบล็อก with ได้อย่างไร?
syntax ใดถูกต้องสำหรับใช้ context manager สองตัวพร้อมกันในบรรทัดเดียว โดยตั้งชื่อให้แต่ละตัว?