2.24. Trình quản lý ngữ cảnh

Câu lệnh with chạy mã thiết lập, sau đó là phần thân, rồi mã dọn dẹp -- với đảm bảo rằng quá trình dọn dẹp sẽ chạy ngay cả khi phần thân thất bại giữa chừng. Cặp phương thức cung cấp thiết lập và dọn dẹp được gọi là trình quản lý ngữ cảnh.

Cú pháp là:

with <expression> as <name>:
    <body>

Biểu thức trả về một trình quản lý ngữ cảnh. Python gọi phương thức __enter__ của nó, tùy chọn gán kết quả cho <name>, chạy phần thân, rồi gọi __exit__ -- dù phần thân hoàn thành bình thường hay ném ra ngoại lệ.

Flow diagram: __enter__ runs first, then the body; the body either completes normally or raises; either way, __exit__ runs at the end.

__exit__ chạy ở cuối khối dù điều gì xảy ra bên trong nó.

2.24.1. Sử dụng trình quản lý ngữ cảnh

Ví dụ điển hình là mở một tệp:

with open("data.txt") as f:
    text = f.read()

# f is now closed, even if read() failed

open() trả về một đối tượng tệp vốn là một trình quản lý ngữ cảnh; __enter__ trả về tệp, và __exit__ đóng nó. Khối with biến "luôn đóng tệp khi xong" thành mặc định thay vì là thứ người gọi phải nhớ.

2.24.1.1. Nhiều trình quản lý ngữ cảnh

Một câu lệnh with duy nhất có thể vào nhiều trình quản lý ngữ cảnh cùng lúc, được phân tách bằng dấu phẩy:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

Tương đương với hai khối with lồng nhau, nhưng phẳng hơn. Các trình quản lý được vào từ trái sang phải và thoát theo thứ tự ngược lại; nếu __enter__ trên trình quản lý bên phải ném ngoại lệ, __exit__ của trình quản lý bên trái vẫn chạy.

2.24.2. Viết một trình quản lý ngữ cảnh

Bất kỳ lớp nào có phương thức __enter____exit__ đều hoạt động như một trình quản lý ngữ cảnh:

class Section:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("---", self.label, "---")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("--- end", self.label, "---")
        return False

with Section("setup"):
    print("doing the work")

Đầu ra:

--- setup ---
doing the work
--- end setup ---

__exit__ nhận ba đối số mô tả ngoại lệ đã kết thúc khối, hoặc ba giá trị None nếu khối hoàn thành bình thường. Trả về False (hoặc None) cho phép bất kỳ ngoại lệ nào truyền đi sau khi dọn dẹp; trả về True sẽ nuốt ngoại lệ đó.

Hãy sử dụng trình quản lý ngữ cảnh cho bất kỳ tài nguyên nào có vòng đời "mở / đóng" hay "chiếm / nhả" -- không chỉ tệp. Mẫu này giữ cho việc dọn dẹp được ghép đôi với thiết lập tại điểm cả hai được giới thiệu, vì vậy một lần đóng bị quên ở giữa một hàm dài không thể làm rò rỉ tài nguyên.