Phân biệt giữa nguồn gốc có thể có của các ngoại lệ được nêu ra từ một withtuyên bố ghép
Phân biệt giữa các trường hợp ngoại lệ xảy ra trong một withtuyên bố là khó khăn bởi vì chúng có thể bắt nguồn từ những nơi khác nhau. Các ngoại lệ có thể được nêu ra từ một trong những vị trí sau (hoặc các hàm được gọi là trong đó):
ContextManager.__init__
ContextManager.__enter__
- cơ thể của
with
ContextManager.__exit__
Để biết thêm chi tiết, xem tài liệu về Các loại Trình quản lý bối cảnh .
Nếu chúng ta muốn phân biệt giữa các trường hợp khác nhau, chỉ cần bọc withvào một try .. exceptlà không đủ. Xem xét ví dụ sau (sử dụng ValueErrorlàm ví dụ nhưng tất nhiên nó có thể được thay thế bằng bất kỳ loại ngoại lệ nào khác):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
Ở đây, exceptsẽ bắt các ngoại lệ bắt nguồn từ tất cả bốn nơi khác nhau và do đó không cho phép phân biệt giữa chúng. Nếu chúng ta di chuyển việc khởi tạo đối tượng quản lý bối cảnh bên ngoài with, chúng ta có thể phân biệt giữa __init__và BLOCK / __enter__ / __exit__:
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
with mgr:
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
# At this point we still cannot distinguish between exceptions raised from
# __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
pass
Thực tế, điều này chỉ giúp với __init__phần nhưng chúng ta có thể thêm một biến sentinel bổ sung để kiểm tra xem phần thân của phần withbắt đầu có thực thi hay không (nghĩa là phân biệt giữa __enter__phần khác và phần khác):
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
try:
entered_body = False
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
else:
# At this point we know the exception came either from BLOCK or from __exit__
pass
Phần khó khăn là phân biệt giữa các ngoại lệ bắt nguồn từ BLOCKvà __exit__bởi vì một ngoại lệ thoát khỏi cơ thể withsẽ được chuyển qua để __exit__có thể quyết định cách xử lý nó (xem tài liệu ). Tuy nhiên, nếu __exit__tăng chính nó, ngoại lệ ban đầu sẽ được thay thế bằng cái mới. Để giải quyết những trường hợp này, chúng ta có thể thêm một exceptmệnh đề chung trong phần thân withđể lưu trữ bất kỳ ngoại lệ tiềm năng nào có thể thoát khỏi không được chú ý và so sánh nó với một mệnh đề được bắt ở ngoài cùng exceptsau này - nếu chúng giống nhau thì điều này có nghĩa là nguồn gốc BLOCKhoặc nếu không nó là __exit__(trong trường hợp __exit__ngăn chặn ngoại lệ bằng cách trả về một giá trị thực ngoài cùngexcept đơn giản là sẽ không được thực thi).
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
entered_body = exc_escaped_from_body = False
try:
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except Exception as err: # this exception would normally escape without notice
# we store this exception to check in the outer `except` clause
# whether it is the same (otherwise it comes from __exit__)
exc_escaped_from_body = err
raise # re-raise since we didn't intend to handle it, just needed to store it
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
elif err is exc_escaped_from_body:
print('BLOCK raised:', err)
else:
print('__exit__ raised:', err)
Phương pháp thay thế bằng cách sử dụng mẫu tương đương được đề cập trong PEP 343
PEP 343 - Tuyên bố "với" chỉ định phiên bản tương đương "không với" của withtuyên bố. Ở đây chúng ta có thể dễ dàng bọc các phần khác nhau try ... exceptvà do đó phân biệt giữa các nguồn lỗi tiềm ẩn khác nhau:
import sys
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
value = type(mgr).__enter__(mgr)
except ValueError as err:
print('__enter__ raised:', err)
else:
exit = type(mgr).__exit__
exc = True
try:
try:
BLOCK
except TypeError:
pass
except:
exc = False
try:
exit_val = exit(mgr, *sys.exc_info())
except ValueError as err:
print('__exit__ raised:', err)
else:
if not exit_val:
raise
except ValueError as err:
print('BLOCK raised:', err)
finally:
if exc:
try:
exit(mgr, None, None, None)
except ValueError as err:
print('__exit__ raised:', err)
Thông thường một cách tiếp cận đơn giản hơn sẽ làm tốt
Nhu cầu xử lý ngoại lệ đặc biệt như vậy nên khá hiếm và thông thường gói toàn bộ withtrong một try ... exceptkhối sẽ là đủ. Đặc biệt nếu các nguồn lỗi khác nhau được biểu thị bằng các loại ngoại lệ (tùy chỉnh) khác nhau (trình quản lý bối cảnh cần được thiết kế phù hợp), chúng ta có thể dễ dàng phân biệt giữa chúng. Ví dụ:
try:
with ContextManager():
BLOCK
except InitError: # raised from __init__
...
except AcquireResourceError: # raised from __enter__
...
except ValueError: # raised from BLOCK
...
except ReleaseResourceError: # raised from __exit__
...
withtuyên bố không kỳ diệu phá vỡ mộttry...excepttuyên bố xung quanh .