Học bằng cách đọc theo thứ tự

contextlib — Quản lý tài nguyên và with tùy chỉnh

Học cách viết with của riêng bạn không cần class bằng @contextmanageryield, dùng try/finally để cleanup chắc chắn, và suppress để nuốt ngoại lệ cụ thể qua ví dụ thật.

contextlib là module để dùng khi bạn muốn viết câu lệnh with của riêng bạn. Chúng ta sẽ đi qua hai công cụ đơn giản nhất theo thứ tự: @contextmanager cho cách dễ nhất để định nghĩa một context manager, và suppress để nuốt các ngoại lệ cụ thể.

@contextmanager — định nghĩa câu lệnh with dễ dàng

Câu lệnh with của Python là cách an toàn để xử lý "thứ phải dọn dẹp sau khi dùng"open file, kết nối DB, giành lock, v.v. Để một class hoạt động với with bằng tay, bạn cần định nghĩa __enter__ / __exit__, đó là khá nhiều boilerplate. @contextmanager là lối tắt: viết một generator duy nhất (hàm chứa yield) và bạn được một context manager hoạt động với with. Code trước yield"khởi đầu hành động", và code sau là "dọn dẹp".

Cấu trúc @contextmanager
@contextmanagerdef section(name):Setup(trước yield)yieldTeardown(sau yield)
Hàm generator cộng @contextmanager — phần trước yield là tương đương __enter__ (setup), và phần sau yield là tương đương __exit__ (teardown). Giá trị yield là cái được gắn bởi as trong câu lệnh with.
from contextlib import contextmanager

# --- Tham khảo: xây class tương thích with ----------
# class Section:
#     def __init__(self, name):
#         self.name = name
#     def __enter__(self):
#         print(f"--- {self.name} bắt đầu ---")
#         return self                 # giá trị được gắn bởi `as` của with
#     def __exit__(self, exc_type, exc, tb):
#         print(f"--- {self.name} kết thúc ---")
# ----------------------------------------------------------

# Phiên bản @contextmanager: một hàm generator làm cùng việc
@contextmanager
def section(name):
    print(f"--- {name} bắt đầu ---")
    yield                       # thân với chạy ở đây
    print(f"--- {name} kết thúc ---")

# Cách dùng
with section("tổng hợp"):
    total = sum(range(100))
    print("Tổng:", total)

# Output:
# --- tổng hợp bắt đầu ---
# Tổng: 4950
# --- tổng hợp kết thúc ---
Phiên bản class → đơn giản hóa thành @contextmanager
Phiên bản class__init__ / __enter__ /__exit__ — 3 methodĐơn giản hóa với@contextmanagerPhiên bản generatortrước yield = setupsau yield = teardown
Thay thế một class với __init__ / __enter__ / __exit__ (ba phương thức) bằng một hàm generator được decorate bằng @contextmanager — code trước/sau yield ánh xạ trực tiếp tới setup/teardown — ít code hơn nhiều.

Dùng try/finally để đảm bảo dọn dẹp ngay khi có ngoại lệ

Code sau yield có thể không chạy nếu có ngoại lệ raise bên trong khối with. Cho dọn dẹp luôn phải xảy ra — đóng file, giải phóng lock — hãy bọc thân generator trong try: yield finally: cleanup() cho an toàn.

Xây transaction(name) mô phỏng giao dịch DB với @contextmanager. In BEGIN trước và COMMIT sau, với SQL bên trong khối hiện ở giữa.

① Hãy import contextmanager từ contextlib

② Hãy định nghĩa hàm transaction(name) được decorate với @contextmanager — in [name] BEGIN, rồi yield, rồi in [name] COMMIT sau

③ Bên trong with transaction("order_create"):, hãy in hai dòng SQL: INSERT INTO orders (id, total) VALUES (1, 1980)UPDATE inventory SET stock = stock - 1

(Nếu code chạy đúng, phần giải thích sẽ hiện ra.)

Python Editor

Chạy code để xem đầu ra

suppress — nuốt ngoại lệ cụ thể

contextlib.suppress(ExceptionType, ...) là context manager để nuốt các ngoại lệ chỉ định và tiếp tục thực thi. Pattern "bỏ qua chỉ ngoại lệ này và đi tiếp" — tương đương try: ... except KeyError: pass — vừa trong một dòng. Bạn có thể liệt kê nhiều kiểu ngoại lệ cùng lúc, và bất cứ gì ngoài danh sách vẫn lan ra bình thường.

from contextlib import suppress
import os

# "Chạy với giá trị mặc định ngay khi file không tồn tại"
config = {"theme": "light", "font_size": 14}
with suppress(FileNotFoundError):
    with open("user_config.txt") as f:
        config["theme"] = f.read().strip()
# Không crash nếu user_config.txt thiếu — config giữ mặc định
print(config)
# → {'theme': 'light', 'font_size': 14}

# "Bỏ qua lặng lẽ nếu đã xóa"
with suppress(FileNotFoundError):
    os.remove("old.tmp")
print("Xóa xong (OK nếu không tồn tại)")

Dùng suppress để tiếp tục ngay khi lấy khóa không tồn tại từ dict.

① Hãy import suppress từ contextlib

② Hãy định nghĩa dict user = {"name": "Minh", "email": "minh@example.com"} (lưu ý: không có khóa age)

③ Bên trong with suppress(KeyError):, hãy thử truy cập user["age"] — xác nhận khóa thiếu không làm crash

④ Ngay sau khối, hãy in Tiếp tục: không crash

⑤ Hãy in tên và email của user trên một dòng dưới dạng Người dùng: ◯ <◯>

Python Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Khi bạn decorate một hàm generator với @contextmanager, tại sao nó cần yield đúng một lần?

Câu 2Bên trong with suppress(KeyError):, điều gì xảy ra khi KeyError được raise?

Câu 3Phần trước yield trong hàm có @contextmanager tương ứng với gì?

Câu 4with suppress(FileNotFoundError): so với try: ... except FileNotFoundError: pass thì: