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?
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 @contextmanager và yield, 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 là "khởi đầu hành động", và code sau là "dọn dẹp".
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 ---
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.
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)")
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
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ì: