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ảnh và Vớ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__()và__exit__(). Việc triển khai mặc định cho__enter__()được cung cấp sẽ trả vềselftrong 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__()và__aexit__(). Việc triển khai mặc định cho__aenter__()được cung cấp sẽ trả vềselftrong 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
withmà không cần tạo lớp hoặc các phương thức__enter__()và__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ớicontextlib.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 lý 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 đề
ascủa câu lệnhwith, 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
withsẽ đượ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ệnhtry...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ệnhwithbiế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ệnhwith.contextmanager()sử dụngContextDecoratornê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ệnhwith. 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" docontextmanager()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 withmà không cần tạo một lớp hoặc các phương thức__aenter__()và__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ệnhasync withthờ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 lý 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
pagemột cách rõ ràng. Ngay cả khi xảy ra lỗi,page.close()sẽ được gọi khi thoát khốiwith.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 dobreakhoặ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ử lý # 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ệnhwith.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
withtăngBaseExceptionGroup, 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ápderive()của nhóm ban đầu.Added in version 3.4.
Thay đổi trong phiên bản 3.12:
suppresshiện hỗ trợ loại bỏ các ngoại lệ được đưa ra như một phần củaBaseExceptionGroup.
- contextlib.redirect_stdout(new_target)¶
Trình quản lý bối cảnh để tạm thời chuyển hướng
sys.stdoutsang 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ượngio.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ệnhwithvới redirect_stdout(io.StringIO()) là 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ườngvới open('help.txt', 'w') là 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.stdoutcó 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ướngsys.stderrsang 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ừ
ContextDecoratorphải triển khai__enter__()và__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đượccontextmanager()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
ContextDecoratorcho 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
ContextDecoratorlàm lớp mixintừ 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ệnhwithrõ ràng bên trong hàm.Added in version 3.2.
- class contextlib.AsyncContextDecorator¶
Tương tự như
ContextDecoratornhư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ảnExitStackvà 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
withlồ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
TypeErrorthay vìAttributeErrornế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.Vì
__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
ExitStackmớ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ệnhwith).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
TypeErrorthay vìAttributeErrornế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 lý bối cảnh nhập ngữ cảnh, Tóm tắtContextManager, ExitStack
lớp Trình quản lý 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 là Không có:
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) là 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 ký
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.
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 lý 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() và 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() là external_stack:
... external_stack.callback(print, "Gọi lại: từ ngữ cảnh bên ngoài")
... với ExitStack() là 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