contextlib --- Tiện ích cho bối cảnh câu lệnh with-

Source code: Lib/contextlib.py


Mô-đun này cung cấp các tiện ích cho các tác vụ phổ biến liên quan đến câu lệnh with. Để biết thêm thông tin, hãy xem thêm Các loại trình quản lý bối cảnhVới Trình quản lý bối cảnh câu lệnh.

Tiện ích

Các chức năng và lớp được cung cấp:

class contextlib.AbstractContextManager

Một abstract base class dành cho các lớp triển khai __enter__()__exit__(). Việc triển khai mặc định cho __enter__() được cung cấp sẽ trả về self trong khi __exit__() là một phương thức trừu tượng mà theo mặc định trả về None. Xem thêm định nghĩa của Các loại trình quản lý bối cảnh.

Added in version 3.6.

class contextlib.AbstractAsyncContextManager

Một abstract base class dành cho các lớp triển khai __aenter__()__aexit__(). Việc triển khai mặc định cho __aenter__() được cung cấp sẽ trả về self trong khi __aexit__() là một phương thức trừu tượng mà theo mặc định trả về None. Xem thêm định nghĩa của Trình quản lý bối cảnh không đồng bộ.

Added in version 3.7.

@contextlib.contextmanager

Hàm này là decorator có thể được sử dụng để xác định hàm xuất xưởng cho trình quản lý bối cảnh câu lệnh with mà không cần tạo lớp hoặc các phương thức __enter__()__exit__() riêng biệt.

Mặc dù nhiều đối tượng vốn hỗ trợ sử dụng bằng câu lệnh with, nhưng đôi khi một tài nguyên cần được quản lý không phải là trình quản lý bối cảnh theo đúng nghĩa của nó và không triển khai phương thức close() để sử dụng với contextlib.closing.

Một ví dụ trừu tượng sẽ như sau để đảm bảo quản lý tài nguyên chính xác

từ trình quản  bối cảnh nhập khẩu contextlib

@contextmanager
def Managed_resource(*args, **kwds):
    # Code để lấy tài nguyên, ví dụ:
    tài nguyên = thu được_resource(*args, **kwds)
    thử:
        nguồn lợi nhuận
    cuối cùng:
        # Code để giải phóng tài nguyên, ví dụ:
        phát hành_resource(tài nguyên)

Chức năng sau đó có thể được sử dụng như thế này

>>> với Managed_resource(timeout=3600) làm tài nguyên:
... # Resource được phát hành ở cuối khối này,
... # even nếu mã trong khối gây ra ngoại lệ

Hàm được trang trí phải trả về generator-iterator khi được gọi. Trình vòng lặp này phải mang lại chính xác một giá trị, giá trị này sẽ được liên kết với các mục tiêu trong mệnh đề as của câu lệnh with, nếu có.

Tại thời điểm trình tạo hoạt động, khối được lồng trong câu lệnh with sẽ được thực thi. Sau đó, trình tạo sẽ được tiếp tục lại sau khi thoát khỏi khối. Nếu một ngoại lệ chưa được xử lý xảy ra trong khối, nó sẽ được kích hoạt lại bên trong trình tạo tại thời điểm xảy ra lỗi. Do đó, bạn có thể sử dụng câu lệnh try...except...finally để bẫy lỗi (nếu có) hoặc đảm bảo rằng một số thao tác dọn dẹp diễn ra. Nếu một ngoại lệ bị giữ lại chỉ để ghi nhật ký hoặc thực hiện một số hành động (thay vì loại bỏ hoàn toàn), trình tạo phải xử lý lại ngoại lệ đó. Nếu không, trình quản lý bối cảnh của trình tạo sẽ cho câu lệnh with biết rằng ngoại lệ đã được xử lý và việc thực thi sẽ tiếp tục với câu lệnh ngay sau câu lệnh with.

contextmanager() sử dụng ContextDecorator nên các trình quản lý bối cảnh mà nó tạo ra có thể được sử dụng làm công cụ trang trí cũng như trong các câu lệnh with. Khi được sử dụng làm công cụ trang trí, một phiên bản trình tạo mới sẽ được tạo ngầm trên mỗi lệnh gọi hàm (điều này cho phép các trình quản lý bối cảnh "một lần" do contextmanager() tạo ra đáp ứng yêu cầu rằng trình quản lý bối cảnh hỗ trợ nhiều lệnh gọi để được sử dụng làm trình trang trí).

Thay đổi trong phiên bản 3.2: Sử dụng ContextDecorator.

@contextlib.asynccontextmanager

Tương tự như contextmanager() nhưng tạo ra asynchronous context manager.

Hàm này là một decorator có thể được sử dụng để xác định hàm xuất xưởng cho các trình quản lý bối cảnh không đồng bộ của câu lệnh async with mà không cần tạo một lớp hoặc các phương thức __aenter__()__aexit__() riêng biệt. Nó phải được áp dụng cho hàm asynchronous generator.

Một ví dụ đơn giản:

từ nhập ngữ cảnh lib asynccontextmanager

@asynccontextmanager
async def get_connection():
    conn = đang chờ thu được_db_connection()
    thử:
        kết nối năng suất
    cuối cùng:
        đang chờ phát hành_db_connection(conn)

async def get_all_users():
    không đồng bộ với get_connection() dưới dạng kết nối:
        return conn.query('SELECT ...')

Added in version 3.7.

Trình quản lý bối cảnh được xác định bằng asynccontextmanager() có thể được sử dụng làm trình trang trí hoặc với câu lệnh async with

thời gian nhập khẩu
từ nhập ngữ cảnh lib asynccontextmanager

@asynccontextmanager
async def timeit():
    bây giờ = time.monotonic()
    thử:
        năng suất
    cuối cùng:
        print(f'it mất {time.monotonic() - now}s để chạy')

@timeit()
async def main():
    # ... mã không đồng bộ ...

Khi được sử dụng làm công cụ trang trí, một phiên bản trình tạo mới sẽ được tạo ngầm trên mỗi lệnh gọi hàm. Điều này cho phép các trình quản lý bối cảnh "một lần" do asynccontextmanager() tạo ra đáp ứng yêu cầu rằng các trình quản lý bối cảnh hỗ trợ nhiều lệnh gọi để được sử dụng làm công cụ trang trí.

Thay đổi trong phiên bản 3.10: Trình quản lý bối cảnh không đồng bộ được tạo bằng asynccontextmanager() có thể được sử dụng làm công cụ trang trí.

contextlib.closing(thing)

Trả về trình quản lý bối cảnh đóng thing sau khi hoàn thành khối. Điều này về cơ bản tương đương với:

từ trình quản  bối cảnh nhập khẩu contextlib

@contextmanager
def đóng (điều):
    thử:
        điều mang lại lợi nhuận
    cuối cùng:
        điều.close()

Và cho phép bạn viết mã như thế này

từ việc đóng nhập ngữ cảnh
từ urllib.request nhập urlopen

với việc đóng(urlopen('https://www.python.org')) dưới dạng trang:
    cho dòng trong trang:
        in (dòng)

mà không cần phải đóng page một cách rõ ràng. Ngay cả khi xảy ra lỗi, page.close() sẽ được gọi khi thoát khối with.

Ghi chú

Hầu hết các loại quản lý tài nguyên đều hỗ trợ giao thức context manager, giao thức này sẽ đóng thing khi thoát khỏi câu lệnh with. Do đó, closing() hữu ích nhất cho các loại bên thứ ba không hỗ trợ trình quản lý bối cảnh. Ví dụ này hoàn toàn nhằm mục đích minh họa, vì urlopen() thường được sử dụng trong trình quản lý ngữ cảnh.

contextlib.aclosing(thing)

Trả về trình quản lý bối cảnh không đồng bộ gọi phương thức aclose() của thing sau khi hoàn thành khối. Điều này về cơ bản tương đương với:

từ nhập ngữ cảnh lib asynccontextmanager

@asynccontextmanager
async def kết thúc (điều):
    thử:
        điều mang lại lợi nhuận
    cuối cùng:
        đang chờ điều.aclose()

Điều đáng chú ý là aclosing() hỗ trợ dọn dẹp xác định các trình tạo không đồng bộ khi chúng thoát ra sớm do break hoặc một ngoại lệ. Ví dụ:

từ quá trình nhập ngữ cảnh

không đồng bộ với aclosing(my_generator()) làm giá trị:
    không đồng bộ cho giá trị trong các giá trị:
        nếu giá trị == 42:
            phá vỡ

Mẫu này đảm bảo rằng mã thoát không đồng bộ của trình tạo được thực thi trong cùng ngữ cảnh với các lần lặp của nó (để các ngoại lệ và biến ngữ cảnh hoạt động như mong đợi và mã thoát không chạy sau vòng đời của một số tác vụ mà nó phụ thuộc vào).

Added in version 3.10.

contextlib.nullcontext(enter_result=None)

Trả về trình quản lý bối cảnh trả về enter_result từ __enter__(), nhưng không làm gì cả. Nó được thiết kế để sử dụng làm công cụ thay thế cho trình quản lý bối cảnh tùy chọn, ví dụ:

def myfunction(arg, ign_Exceptions=False):
    nếu bỏ qua_ngoại lệ:
        # Use ngăn chặn để bỏ qua tất cả các ngoại lệ.
        cm = contextlib.suppress(Ngoại lệ)
    khác:
        # Do không bỏ qua bất kỳ trường hợp ngoại lệ nào, cm không có tác dụng.
        cm = contextlib.nullcontext()
    với cm:
        # Do gì đó

Một ví dụ sử dụng enter_result:

def process_file(file_or_path):
    nếu isinstance(file_or_path, str):
        chuỗi # If, mở tệp
        cm = open(file_or_path)
    khác:
        # Caller chịu trách nhiệm đóng tập tin
        cm = nullcontext(file_or_path)

    với cm dưới dạng tệp:
        xử  # Perform trên tập tin

Nó cũng có thể được sử dụng làm dự phòng cho asynchronous context managers:

async def send_http(session=None):
    nếu không phiên:
        # If không có phiên http, hãy tạo nó bằng aiohttp
        cm = aiohttp.ClientSession()
    khác:
        # Caller chịu trách nhiệm đóng phiên
        cm = nullcontext(phiên)

    không đồng bộ với cm dưới dạng phiên:
        yêu cầu http # Send với phiên

Added in version 3.7.

Thay đổi trong phiên bản 3.10: hỗ trợ asynchronous context manager đã được thêm vào.

contextlib.suppress(*exceptions)

Trả về trình quản lý bối cảnh ngăn chặn bất kỳ trường hợp ngoại lệ nào được chỉ định nếu chúng xuất hiện trong phần nội dung của câu lệnh with, sau đó tiếp tục thực thi với câu lệnh đầu tiên sau khi kết thúc câu lệnh with.

Giống như bất kỳ cơ chế nào khác ngăn chặn hoàn toàn các ngoại lệ, trình quản lý bối cảnh này chỉ nên được sử dụng để xử lý các lỗi rất cụ thể trong đó việc tiếp tục âm thầm thực thi chương trình được biết là điều nên làm.

Ví dụ:

từ ngăn chặn nhập ngữ cảnh

với tính năng triệt tiêu (FileNotFoundError):
    os.remove('somefile.tmp')

với tính năng triệt tiêu (FileNotFoundError):
    os.remove('someotherfile.tmp')

Mã này tương đương với:

thử:
    os.remove('somefile.tmp')
ngoại trừ FileNotFoundError:
    vượt qua

thử:
    os.remove('someotherfile.tmp')
ngoại trừ FileNotFoundError:
    vượt qua

Trình quản lý bối cảnh này là reentrant.

Nếu mã trong khối with tăng BaseExceptionGroup, các ngoại lệ bị loại bỏ sẽ bị xóa khỏi nhóm. Bất kỳ trường hợp ngoại lệ nào của nhóm không bị chặn sẽ được đưa lại vào nhóm mới được tạo bằng phương pháp derive() của nhóm ban đầu.

Added in version 3.4.

Thay đổi trong phiên bản 3.12: suppress hiện hỗ trợ loại bỏ các ngoại lệ được đưa ra như một phần của BaseExceptionGroup.

contextlib.redirect_stdout(new_target)

Trình quản lý bối cảnh để tạm thời chuyển hướng sys.stdout sang một tệp khác hoặc đối tượng giống như tệp.

Công cụ này tăng thêm tính linh hoạt cho các hàm hoặc lớp hiện có có đầu ra được gắn cứng vào thiết bị xuất chuẩn.

Ví dụ: đầu ra của help() thường được gửi tới sys.stdout. Bạn có thể nắm bắt đầu ra đó trong một chuỗi bằng cách chuyển hướng đầu ra đến đối tượng io.StringIO. Luồng thay thế được trả về từ phương thức __enter__() và do đó có sẵn làm mục tiêu của câu lệnh with

với redirect_stdout(io.StringIO())  f:
    giúp đỡ(pow)
s = f.getvalue()

Để gửi đầu ra của help() tới một tệp trên đĩa, hãy chuyển hướng đầu ra sang một tệp thông thường

với open('help.txt', 'w')  f:
    với redirect_stdout(f):
        giúp đỡ(pow)

Để gửi đầu ra của help() đến sys.stderr:

với redirect_stdout(sys.stderr):
    giúp đỡ(pow)

Lưu ý rằng tác dụng phụ toàn cầu trên sys.stdout có nghĩa là trình quản lý bối cảnh này không phù hợp để sử dụng trong mã thư viện và hầu hết các ứng dụng theo luồng. Nó cũng không ảnh hưởng đến đầu ra của các quy trình con. Tuy nhiên, nó vẫn là một cách tiếp cận hữu ích cho nhiều tập lệnh tiện ích.

Trình quản lý bối cảnh này là reentrant.

Added in version 3.4.

contextlib.redirect_stderr(new_target)

Tương tự như redirect_stdout() nhưng chuyển hướng sys.stderr sang một tệp khác hoặc đối tượng giống như tệp.

Trình quản lý bối cảnh này là reentrant.

Added in version 3.5.

contextlib.chdir(path)

Trình quản lý bối cảnh không an toàn song song để thay đổi thư mục làm việc hiện tại. Vì điều này thay đổi trạng thái chung, thư mục làm việc, nên nó không phù hợp để sử dụng trong hầu hết các bối cảnh theo luồng hoặc không đồng bộ. Nó cũng không phù hợp với hầu hết việc thực thi mã phi tuyến tính, như trình tạo, trong đó việc thực thi chương trình tạm thời bị hủy bỏ -- trừ khi có mong muốn rõ ràng, bạn không nên nhường đường khi trình quản lý bối cảnh này đang hoạt động.

Đây là một trình bao bọc đơn giản xung quanh chdir(), nó thay đổi thư mục làm việc hiện tại khi nhập và khôi phục thư mục cũ khi thoát.

Trình quản lý bối cảnh này là reentrant.

Added in version 3.11.

class contextlib.ContextDecorator

Một lớp cơ sở cho phép sử dụng trình quản lý bối cảnh làm công cụ trang trí.

Trình quản lý bối cảnh kế thừa từ ContextDecorator phải triển khai __enter__()__exit__() như bình thường. __exit__ vẫn giữ lại khả năng xử lý ngoại lệ tùy chọn ngay cả khi được sử dụng làm trang trí.

ContextDecorator được contextmanager() sử dụng, vì vậy bạn sẽ tự động có được chức năng này.

Ví dụ về ContextDecorator:

từ nhập ngữ cảnh vào ContextDecorator

lớp mycontext(ContextDecorator):
    chắc chắn __enter__(tự):
        in('Bắt đầu')
        tự trở về

    def __exit__(self, *exc):
        in('Hoàn tất')
        trả về Sai

Lớp sau đó có thể được sử dụng như thế này

>>> @mycontext()
... hàm def():
... print('Phần ở giữa')
...
>>> hàm()
Bắt đầu
Phần ở giữa
hoàn thiện

>>> với mycontext():
... print('Phần ở giữa')
...
Bắt đầu
Phần ở giữa
hoàn thiện

Sự thay đổi này chỉ là cú pháp cho bất kỳ cấu trúc nào có dạng sau:

chắc chắn f():
    với cm():
        thứ # Do

ContextDecorator cho phép bạn viết:

@cm()
chắc chắn f():
    thứ # Do

Nó cho thấy rõ rằng cm áp dụng cho toàn bộ chức năng, thay vì chỉ một phần của nó (và việc lưu mức thụt đầu dòng cũng là điều tốt).

Các trình quản lý bối cảnh hiện tại đã có lớp cơ sở có thể được mở rộng bằng cách sử dụng ContextDecorator làm lớp mixin

từ nhập ngữ cảnh vào ContextDecorator

lớp mycontext(ContextBaseClass, ContextDecorator):
    chắc chắn __enter__(tự):
        tự trở về

    def __exit__(self, *exc):
        trả về Sai

Ghi chú

Vì hàm trang trí phải có thể được gọi nhiều lần nên trình quản lý bối cảnh cơ bản phải hỗ trợ sử dụng trong nhiều câu lệnh with. Nếu không phải như vậy thì nên sử dụng cấu trúc ban đầu với câu lệnh with rõ ràng bên trong hàm.

Added in version 3.2.

class contextlib.AsyncContextDecorator

Tương tự như ContextDecorator nhưng chỉ dành cho các hàm không đồng bộ.

Ví dụ về AsyncContextDecorator:

từ quá trình nhập asyncio
từ nhập ngữ cảnh AsyncContextDecorator

lớp mycontext(AsyncContextDecorator):
    async def __aenter__(self):
        in('Bắt đầu')
        tự trở về

    async def __aexit__(self, *exc):
        in('Hoàn tất')
        trả về Sai

Lớp sau đó có thể được sử dụng như thế này

>>> @mycontext()
... hàm def không đồng bộ():
... print('Phần ở giữa')
...
>>> chạy(hàm())
Bắt đầu
Phần ở giữa
hoàn thiện

>>> hàm def không đồng bộ():
... không đồng bộ với mycontext():
... print('Phần ở giữa')
...
>>> chạy(hàm())
Bắt đầu
Phần ở giữa
hoàn thiện

Added in version 3.10.

class contextlib.ExitStack

Trình quản lý bối cảnh được thiết kế để giúp dễ dàng kết hợp các chức năng dọn dẹp và trình quản lý bối cảnh khác theo chương trình, đặc biệt là các chức năng tùy chọn hoặc được điều khiển bởi dữ liệu đầu vào.

Ví dụ: một tập hợp các tệp có thể dễ dàng được xử lý trong một câu lệnh with như sau

với ExitStack() dưới dạng ngăn xếp:
    files = [stack.enter_context(open(fname)) cho fname trong tên tệp]
    Các tập tin đã mở # All sẽ tự động bị đóng khi kết thúc
    # the bằng câu lệnh, ngay cả khi cố gắng mở tệp sau
    # in danh sách đưa ra một ngoại lệ

Phương thức __enter__() trả về phiên bản ExitStack và không thực hiện thao tác bổ sung nào.

Mỗi phiên bản duy trì một chồng các lệnh gọi lại đã đăng ký được gọi theo thứ tự ngược lại khi phiên bản được đóng (rõ ràng hoặc ngầm định ở cuối câu lệnh with). Lưu ý rằng các lệnh gọi lại not được gọi ngầm khi phiên bản ngăn xếp ngữ cảnh được thu thập rác.

Mô hình ngăn xếp này được sử dụng để các trình quản lý bối cảnh thu thập tài nguyên của chúng theo phương thức __init__ (chẳng hạn như đối tượng tệp) có thể được xử lý chính xác.

Vì các lệnh gọi lại đã đăng ký được gọi theo thứ tự đăng ký ngược lại nên điều này sẽ hoạt động như thể nhiều câu lệnh with lồng nhau đã được sử dụng với tập hợp các lệnh gọi lại đã đăng ký. Điều này thậm chí còn mở rộng sang việc xử lý ngoại lệ - nếu lệnh gọi lại bên trong ngăn chặn hoặc thay thế một ngoại lệ thì lệnh gọi lại bên ngoài sẽ được chuyển đối số dựa trên trạng thái cập nhật đó.

Đây là một API cấp độ tương đối thấp, đảm nhiệm các chi tiết trong việc giải phóng chính xác các lệnh gọi lại thoát. Nó cung cấp nền tảng phù hợp cho những người quản lý bối cảnh cấp cao hơn thao tác ngăn xếp thoát theo những cách cụ thể của ứng dụng.

Added in version 3.3.

enter_context(cm)

Nhập trình quản lý bối cảnh mới và thêm phương thức __exit__() của nó vào ngăn xếp gọi lại. Giá trị trả về là kết quả của phương thức __enter__() của chính trình quản lý bối cảnh.

Các trình quản lý bối cảnh này có thể loại bỏ các ngoại lệ giống như bình thường nếu được sử dụng trực tiếp như một phần của câu lệnh with.

Thay đổi trong phiên bản 3.11: Tăng TypeError thay vì AttributeError nếu cm không phải là trình quản lý bối cảnh.

push(exit)

Thêm phương thức __exit__() của trình quản lý bối cảnh vào ngăn xếp gọi lại.

__enter__ được gọi not nên phương pháp này có thể được sử dụng để bao quát một phần của quá trình triển khai __enter__() bằng phương thức __exit__() của chính trình quản lý bối cảnh.

Nếu được truyền một đối tượng không phải là trình quản lý bối cảnh, phương thức này sẽ giả sử đó là một lệnh gọi lại có cùng chữ ký với phương thức __exit__() của trình quản lý ngữ cảnh và thêm nó trực tiếp vào ngăn xếp gọi lại.

Bằng cách trả về các giá trị thực, các lệnh gọi lại này có thể loại bỏ các ngoại lệ giống như cách mà các phương thức __exit__() của trình quản lý bối cảnh có thể làm.

Đối tượng được truyền vào được trả về từ hàm, cho phép phương thức này được sử dụng làm công cụ trang trí hàm.

callback(callback, /, *args, **kwds)

Chấp nhận một hàm và đối số gọi lại tùy ý rồi thêm nó vào ngăn xếp gọi lại.

Không giống như các phương pháp khác, lệnh gọi lại được thêm theo cách này không thể loại bỏ ngoại lệ (vì chúng không bao giờ được chuyển chi tiết ngoại lệ).

Lệnh gọi lại được truyền vào được trả về từ hàm, cho phép sử dụng phương thức này làm công cụ trang trí hàm.

pop_all()

Chuyển ngăn xếp gọi lại sang phiên bản ExitStack mới và trả về nó. Thao tác này không gọi lại lệnh gọi lại nào - thay vào đó, giờ đây chúng sẽ được gọi khi ngăn xếp mới bị đóng (rõ ràng hoặc ngầm định ở cuối câu lệnh with).

Ví dụ: một nhóm tệp có thể được mở dưới dạng thao tác "tất cả hoặc không có gì" như sau:

với ExitStack() dưới dạng ngăn xếp:
    files = [stack.enter_context(open(fname)) cho fname trong tên tệp]
    # Hold vào phương thức đóng, nhưng chưa gọi nó.
    close_files = stack.pop_all().close
    # If mở bất kỳ tệp nào không thành công, tất cả các tệp đã mở trước đó sẽ bị lỗi
    # closed tự động. Nếu tất cả các tập tin được mở thành công,
    # they sẽ vẫn mở ngay cả sau khi câu lệnh with kết thúc.
    # close_files() sau đó có thể được gọi một cách rõ ràng để đóng tất cả.
close()

Ngay lập tức hủy bỏ ngăn xếp lệnh gọi lại, gọi lệnh gọi lại theo thứ tự ngược lại với đăng ký. Đối với mọi trình quản lý bối cảnh và lệnh gọi lại thoát đã đăng ký, các đối số được truyền vào sẽ cho biết rằng không có ngoại lệ nào xảy ra.

class contextlib.AsyncExitStack

Một asynchronous context manager, tương tự như ExitStack, hỗ trợ kết hợp cả trình quản lý bối cảnh đồng bộ và không đồng bộ, cũng như có các coroutine để dọn dẹp logic.

Phương pháp close() không được triển khai; aclose() phải được sử dụng thay thế.

async enter_async_context(cm)

Tương tự như ExitStack.enter_context() nhưng yêu cầu trình quản lý bối cảnh không đồng bộ.

Thay đổi trong phiên bản 3.11: Tăng TypeError thay vì AttributeError nếu cm không phải là trình quản lý bối cảnh không đồng bộ.

push_async_exit(exit)

Tương tự như ExitStack.push() nhưng yêu cầu trình quản lý bối cảnh không đồng bộ hoặc chức năng coroutine.

push_async_callback(callback, /, *args, **kwds)

Tương tự như ExitStack.callback() nhưng yêu cầu chức năng coroutine.

async aclose()

Tương tự như ExitStack.close() nhưng xử lý đúng cách các chờ đợi.

Tiếp tục ví dụ cho asynccontextmanager():

không đồng bộ với AsyncExitStack() dưới dạng ngăn xếp:
    kết nối = [đang chờ stack.enter_async_context(get_connection())
        cho tôi trong phạm vi (5)]
    các kết nối đã mở # All sẽ tự động được giải phóng vào cuối
    # the không đồng bộ với câu lệnh, ngay cả khi cố gắng mở kết nối
    # later trong danh sách đưa ra một ngoại lệ.

Added in version 3.7.

Ví dụ và Bí quyết

Phần này mô tả một số ví dụ và công thức để sử dụng hiệu quả các công cụ do contextlib cung cấp.

Hỗ trợ một số lượng lớn các trình quản lý bối cảnh

Trường hợp sử dụng chính của ExitStack là trường hợp được đưa ra trong tài liệu lớp: hỗ trợ nhiều trình quản lý bối cảnh và các hoạt động dọn dẹp khác trong một câu lệnh with duy nhất. Sự thay đổi có thể đến từ số lượng trình quản lý bối cảnh cần thiết được điều khiển bởi đầu vào của người dùng (chẳng hạn như mở bộ sưu tập tệp do người dùng chỉ định) hoặc từ một số trình quản lý bối cảnh là tùy chọn

với ExitStack() dưới dạng ngăn xếp:
    cho tài nguyên trong tài nguyên:
        stack.enter_context(tài nguyên)
    nếu cần_special_resource():
        đặc biệt = thu được_special_resource()
        stack.callback(release_special_resource, đặc biệt)
    Hoạt động # Perform sử dụng tài nguyên có được

Như được hiển thị, ExitStack cũng giúp bạn dễ dàng sử dụng các câu lệnh with để quản lý các tài nguyên tùy ý vốn không hỗ trợ giao thức quản lý ngữ cảnh.

Bắt ngoại lệ từ các phương pháp __enter__

Đôi khi, người ta mong muốn nắm bắt các ngoại lệ từ việc triển khai phương thức __enter__(), without vô tình bắt được các ngoại lệ từ nội dung câu lệnh with hoặc phương thức __exit__() của trình quản lý bối cảnh. Bằng cách sử dụng ExitStack, các bước trong giao thức quản lý ngữ cảnh có thể được tách ra một chút để cho phép điều này

ngăn xếp = ExitStack()
thử:
    x = stack.enter_context(cm)
ngoại trừ Ngoại lệ:
    # handle __enter__ ngoại lệ
khác:
    với ngăn xếp:
        # Handle trường hợp bình thường

Trên thực tế, việc cần phải làm điều này có thể chỉ ra rằng API cơ bản phải cung cấp giao diện quản lý tài nguyên trực tiếp để sử dụng với các câu lệnh try/except/finally, nhưng không phải tất cả các API đều được thiết kế tốt về mặt đó. Khi trình quản lý bối cảnh là công cụ quản lý tài nguyên duy nhất mà API được cung cấp thì ExitStack có thể giúp xử lý các tình huống khác nhau không thể xử lý trực tiếp trong câu lệnh with dễ dàng hơn.

Dọn dẹp trong triển khai __enter__

Như đã lưu ý trong tài liệu của ExitStack.push(), phương pháp này có thể hữu ích trong việc dọn sạch tài nguyên đã được phân bổ nếu các bước triển khai __enter__() sau đó không thành công.

Dưới đây là ví dụ về cách thực hiện việc này đối với trình quản lý bối cảnh chấp nhận các chức năng thu thập và giải phóng tài nguyên, cùng với chức năng xác thực tùy chọn và ánh xạ chúng tới giao thức quản lý bối cảnh:

từ trình quản  bối cảnh nhập ngữ cảnh, Tóm tắtContextManager, ExitStack

lớp Trình quản  tài nguyên (AbstractContextManager):

    def __init__(self, Acacqui_resource, Release_resource, check_resource_ok=Không):
        self.acquire_resource = thu được_resource
        self.release_resource=release_resource
        nếu check_resource_ok  Không :
            def check_resource_ok(tài nguyên):
                trả về Đúng
        self.check_resource_ok = check_resource_ok

    @contextmanager
    def _cleanup_on_error(tự):
        với ExitStack() dưới dạng ngăn xếp:
            stack.push(tự)
            năng suất
            Kiểm tra xác thực # The đã vượt qua và không đưa ra ngoại lệ
            # Accordingly, chúng tôi muốn giữ tài nguyên và chuyển nó
            # back cho người gọi của chúng tôi
            stack.pop_all()

    chắc chắn __enter__(tự):
        tài nguyên = self.acquire_resource()
        với self._cleanup_on_error():
            nếu không self.check_resource_ok(resource):
                msg = "Xác thực thất bại cho {!r}"
                raise RuntimeError(msg.format(resource))
        trả lại tài nguyên

    def __exit__(self, *exc_details):
        # We không cần sao chép bất kỳ logic phát hành tài nguyên nào của chúng tôi
        self.release_resource()

Thay thế việc sử dụng các biến try-finally và flag

Một mẫu đôi khi bạn sẽ thấy là câu lệnh try-finally với biến cờ để cho biết liệu phần nội dung của mệnh đề finally có nên được thực thi hay không. Ở dạng đơn giản nhất (không thể xử lý được chỉ bằng cách sử dụng mệnh đề except), nó trông giống như thế này

cleanup_ Need = Đúng
thử:
    kết quả = performance_Operation()
    nếu kết quả:
        cleanup_ Need = Sai
cuối cùng:
    nếu cleanup_ Need:
        dọn dẹp_resource()

Giống như bất kỳ mã dựa trên câu lệnh try nào, điều này có thể gây ra sự cố cho quá trình phát triển và đánh giá vì mã thiết lập và mã dọn dẹp có thể bị phân tách bằng các đoạn mã dài tùy ý.

Thay vào đó, ExitStack có thể đăng ký một lệnh gọi lại để thực thi ở cuối câu lệnh with và sau đó quyết định bỏ qua việc thực hiện lệnh gọi lại đó:

từ nhập khẩu bối cảnh ExitStack

với ExitStack() dưới dạng ngăn xếp:
    stack.callback(cleanup_resources)
    kết quả = performance_Operation()
    nếu kết quả:
        stack.pop_all()

Điều này cho phép hành vi dọn dẹp dự định được thực hiện rõ ràng ngay từ đầu, thay vì yêu cầu một biến cờ riêng.

Nếu một ứng dụng cụ thể sử dụng mẫu này nhiều, nó có thể được đơn giản hóa hơn nữa bằng một lớp trợ giúp nhỏ

từ nhập khẩu bối cảnh ExitStack

Gọi lại lớp (ExitStack):
    def __init__(self, callback, /, *args, **kwds):
        siêu().__init__()
        self.callback(gọi lại, *args, **kwds)

    def hủy (tự):
        tự.pop_all()

với Callback(cleanup_resources)  cb:
    kết quả = performance_Operation()
    nếu kết quả:
        cb.cancel()

Nếu việc dọn dẹp tài nguyên chưa được gói gọn gàng thành một hàm độc lập thì vẫn có thể sử dụng dạng trang trí của ExitStack.callback() để khai báo trước việc dọn dẹp tài nguyên:

từ nhập khẩu bối cảnh ExitStack

với ExitStack() dưới dạng ngăn xếp:
    @stack.callback
    def cleanup_resource():
        ...
    kết quả = performance_Operation()
    nếu kết quả:
        stack.pop_all()

Do cách hoạt động của giao thức trang trí, hàm gọi lại được khai báo theo cách này không thể nhận bất kỳ tham số nào. Thay vào đó, mọi tài nguyên được giải phóng phải được truy cập dưới dạng các biến đóng.

Sử dụng trình quản lý bối cảnh làm công cụ trang trí chức năng

ContextDecorator cho phép sử dụng trình quản lý bối cảnh trong cả câu lệnh with thông thường và cũng như một công cụ trang trí hàm.

Ví dụ: đôi khi rất hữu ích khi bọc các hàm hoặc nhóm câu lệnh bằng một trình ghi nhật ký có thể theo dõi thời gian vào và thời gian thoát. Thay vì viết cả trình trang trí hàm và trình quản lý bối cảnh cho tác vụ, việc kế thừa từ ContextDecorator cung cấp cả hai khả năng trong một định nghĩa duy nhất:

từ nhập ngữ cảnh vào ContextDecorator
nhập nhật 

logging.basicConfig(level=logging.INFO)

lớp track_entry_and_exit(ContextDecorator):
    def __init__(bản thân, tên):
        self.name = tên

    chắc chắn __enter__(tự):
        logging.info('Đang nhập: %s', self.name)

    def __exit__(self, ex_type, exc, ex_tb):
        logging.info('Đang thoát: %s', self.name)

Các thể hiện của lớp này có thể được sử dụng làm cả trình quản lý bối cảnh

với track_entry_and_exit('trình tải widget'):
    print('Một số hoạt động tiêu tốn thời gian sẽ diễn ra ở đây')
    tải_widget()

Và cũng như một người trang trí chức năng

@track_entry_and_exit('trình tải widget')
hoạt động chắc chắn():
    print('Một số hoạt động tiêu tốn thời gian sẽ diễn ra ở đây')
    tải_widget()

Lưu ý rằng có một hạn chế bổ sung khi sử dụng trình quản lý bối cảnh làm công cụ trang trí hàm: không có cách nào để truy cập giá trị trả về của __enter__(). Nếu giá trị đó là cần thiết thì vẫn cần sử dụng câu lệnh with rõ ràng.

Xem thêm

PEP 343 - Câu lệnh "với"

Thông số kỹ thuật, nền tảng và ví dụ cho câu lệnh with của Python.

Trình quản lý bối cảnh sử dụng một lần, có thể tái sử dụng và đăng ký lại

Hầu hết các trình quản lý bối cảnh được viết theo cách có nghĩa là chúng chỉ có thể được sử dụng hiệu quả trong câu lệnh with một lần. Các trình quản lý bối cảnh sử dụng một lần này phải được tạo lại mỗi lần chúng được sử dụng - việc cố gắng sử dụng chúng lần thứ hai sẽ gây ra ngoại lệ hoặc không hoạt động chính xác.

Hạn chế chung này có nghĩa là nói chung nên tạo trình quản lý bối cảnh trực tiếp trong tiêu đề của câu lệnh with nơi chúng được sử dụng (như được hiển thị trong tất cả các ví dụ sử dụng ở trên).

Tệp là một ví dụ về trình quản lý bối cảnh sử dụng một lần hiệu quả, vì câu lệnh with đầu tiên sẽ đóng tệp, ngăn chặn mọi thao tác IO tiếp theo bằng cách sử dụng đối tượng tệp đó.

Trình quản lý bối cảnh được tạo bằng contextmanager() cũng là trình quản lý bối cảnh sử dụng một lần và sẽ phàn nàn về việc trình tạo cơ bản không hoạt động nếu cố gắng sử dụng chúng lần thứ hai:

>>> từ trình quản  bối cảnh nhập ngữ cảnh
>>> @contextmanager
... def sử dụng đơn():
... print("Trước")
... năng suất
... in("Sau")
...
>>> cm = sử dụng một lần()
>>> với cm:
... vượt qua
...
trước đây
Sau
>>> với cm:
... vượt qua
...
Traceback (cuộc gọi gần đây nhất):
    ...
RuntimeError: trình tạo không hoạt động

Trình quản lý bối cảnh Reentrant

Các nhà quản lý bối cảnh phức tạp hơn có thể được "tái nhập". Các trình quản lý bối cảnh này không chỉ có thể được sử dụng trong nhiều câu lệnh with mà còn có thể được sử dụng inside một câu lệnh with đã sử dụng cùng một trình quản lý bối cảnh.

threading.RLock là một ví dụ về trình quản lý bối cảnh được đăng ký lại, cũng như suppress(), redirect_stdout()chdir(). Đây là một ví dụ rất đơn giản về việc sử dụng reentrant:

>>> từ nhập khẩu contextlib redirect_stdout
>>> từ io nhập StringIO
>>> luồng = StringIO()
>>> write_to_stream = redirect_stdout(stream)
>>> với write_to_stream:
... print("Điều này được ghi vào luồng chứ không phải thiết bị xuất chuẩn")
... với write_to_stream:
... print("Điều này cũng được ghi vào luồng")
...
>>> print("Điều này được ghi trực tiếp vào thiết bị xuất chuẩn")
Điều này được viết trực tiếp vào thiết bị xuất chuẩn
>>> in(stream.getvalue())
Điều này được ghi vào luồng chứ không phải thiết bị xuất chuẩn
Điều này cũng được ghi vào luồng

Các ví dụ thực tế về việc quay lại có nhiều khả năng liên quan đến nhiều hàm gọi lẫn nhau và do đó phức tạp hơn nhiều so với ví dụ này.

Cũng lưu ý rằng việc được đăng ký lại là not cũng giống như việc đảm bảo an toàn cho luồng. Ví dụ: redirect_stdout() chắc chắn không an toàn cho luồng vì nó thực hiện sửa đổi toàn cầu đối với trạng thái hệ thống bằng cách liên kết sys.stdout với một luồng khác.

Trình quản lý bối cảnh có thể tái sử dụng

Khác biệt với cả trình quản lý bối cảnh sử dụng một lần và trình quản lý bối cảnh được cấp lại là các trình quản lý bối cảnh "có thể tái sử dụng" (hoặc nói một cách hoàn toàn rõ ràng là các trình quản lý bối cảnh "có thể tái sử dụng nhưng không thể cấp lại", vì các trình quản lý bối cảnh được cấp lại cũng có thể tái sử dụng). Các trình quản lý bối cảnh này hỗ trợ việc sử dụng nhiều lần nhưng sẽ không thành công (hoặc không hoạt động chính xác) nếu phiên bản trình quản lý bối cảnh cụ thể đã được sử dụng trong câu lệnh chứa with.

threading.Lock là một ví dụ về trình quản lý bối cảnh có thể tái sử dụng nhưng không được cấp lại (đối với khóa đăng nhập lại, cần phải sử dụng threading.RLock thay thế).

Một ví dụ khác về trình quản lý bối cảnh có thể tái sử dụng nhưng không được cấp lại là ExitStack, vì nó gọi các lệnh gọi lại all hiện đã đăng ký khi để lại bất kỳ câu lệnh nào, bất kể các lệnh gọi lại đó được thêm vào ở đâu:

>>> từ nhập khẩu bối cảnh ExitStack
>>> ngăn xếp = ExitStack()
>>> với ngăn xếp:
... stack.callback(print, "Gọi lại: từ ngữ cảnh đầu tiên")
... print("Rời khỏi ngữ cảnh đầu tiên")
...
Rời khỏi bối cảnh đầu tiên
Gọi lại: từ bối cảnh đầu tiên
>>> với ngăn xếp:
... stack.callback(print, "Gọi lại: từ ngữ cảnh thứ hai")
... print("Rời khỏi bối cảnh thứ hai")
...
Rời khỏi bối cảnh thứ hai
Gọi lại: từ bối cảnh thứ hai
>>> với ngăn xếp:
... stack.callback(print, "Gọi lại: từ ngữ cảnh bên ngoài")
... với ngăn xếp:
... stack.callback(print, "Gọi lại: từ ngữ cảnh bên trong")
... print("Rời khỏi bối cảnh bên trong")
... print("Rời khỏi bối cảnh bên ngoài")
...
Rời khỏi bối cảnh bên trong
Gọi lại: từ bối cảnh bên trong
Gọi lại: từ bối cảnh bên ngoài
Rời khỏi bối cảnh bên ngoài

Như kết quả đầu ra từ ví dụ cho thấy, việc sử dụng lại một đối tượng ngăn xếp duy nhất trên nhiều đối tượng có câu lệnh hoạt động chính xác, nhưng việc cố gắng lồng chúng sẽ khiến ngăn xếp bị xóa ở cuối câu lệnh with trong cùng, đây khó có thể là hành vi mong muốn.

Sử dụng các phiên bản ExitStack riêng biệt thay vì sử dụng lại một phiên bản duy nhất sẽ tránh được vấn đề đó

>>> từ nhập khẩu bối cảnh ExitStack
>>> với ExitStack()  external_stack:
... external_stack.callback(print, "Gọi lại: từ ngữ cảnh bên ngoài")
... với ExitStack()  Inner_stack:
... Inner_stack.callback(print, "Gọi lại: từ ngữ cảnh bên trong")
... print("Rời khỏi bối cảnh bên trong")
... print("Rời khỏi bối cảnh bên ngoài")
...
Rời khỏi bối cảnh bên trong
Gọi lại: từ bối cảnh bên trong
Rời khỏi bối cảnh bên ngoài
Gọi lại: từ bối cảnh bên ngoài