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 with
tuyê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 with
tuyê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 with
vào một try .. except
là không đủ. Xem xét ví dụ sau (sử dụng ValueError
là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, except
sẽ 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 with
bắ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ừ BLOCK
và __exit__
bởi vì một ngoại lệ thoát khỏi cơ thể with
sẽ đượ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 except
mệ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 except
sau này - nếu chúng giống nhau thì điều này có nghĩa là nguồn gốc BLOCK
hoặ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 with
tuyên bố. Ở đây chúng ta có thể dễ dàng bọc các phần khác nhau try ... except
và 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ộ with
trong một try ... except
khố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__
...
with
tuyên bố không kỳ diệu phá vỡ mộttry...except
tuyên bố xung quanh .