ความแตกต่างระหว่างต้นกำเนิดที่เป็นไปได้ของข้อยกเว้นที่ยกมาจากwith
คำสั่งผสม
การแยกความแตกต่างระหว่างข้อยกเว้นที่เกิดขึ้นในwith
คำสั่งนั้นเป็นเรื่องที่ยุ่งยากเพราะพวกมันสามารถเกิดขึ้นได้ในที่ต่าง ๆ สามารถยกข้อยกเว้นจากสถานที่ต่อไปนี้ (หรือฟังก์ชันที่เรียกว่า):
ContextManager.__init__
ContextManager.__enter__
- ร่างกายของ
with
ContextManager.__exit__
สำหรับรายละเอียดเพิ่มเติมโปรดดูเอกสารเกี่ยวกับประเภทผู้จัดการบริบท
หากเราต้องการแยกความแตกต่างระหว่างกรณีที่แตกต่างกันเพียงแค่การตัดคำwith
ใน a try .. except
ไม่เพียงพอ ลองพิจารณาตัวอย่างต่อไปนี้ (ใช้ValueError
เป็นตัวอย่าง แต่แน่นอนว่ามันสามารถทดแทนด้วยประเภทข้อยกเว้นอื่น ๆ ):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
ที่นี่except
จะจับข้อยกเว้นที่เกิดขึ้นในสถานที่ทั้งสี่ที่แตกต่างกันและดังนั้นจึงไม่อนุญาตให้มีความแตกต่างระหว่างพวกเขา หากเราย้ายอินสแตนซ์ของวัตถุตัวจัดการบริบทนอกwith
เราสามารถแยกความแตกต่างระหว่าง__init__
และ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
สิ่งนี้ช่วยได้อย่างมีประสิทธิภาพ__init__
แต่เราสามารถเพิ่มตัวแปรแมวมองพิเศษเพื่อตรวจสอบว่าร่างกายของผู้ที่with
เริ่มต้นจะดำเนินการ (เช่นการแยกความแตกต่างระหว่าง__enter__
และอื่น ๆ ):
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
ส่วนที่ยุ่งยากคือการแยกแยะความแตกต่างระหว่างข้อยกเว้นที่เกิดขึ้นจากBLOCK
และ__exit__
เนื่องจากข้อยกเว้นที่หลบหนีร่างกายของwith
จะถูกส่งผ่านไป__exit__
ซึ่งสามารถตัดสินใจได้ว่าจะจัดการกับมันอย่างไร (ดูเอกสาร ) หากมีการ__exit__
ยกตัวเองข้อยกเว้นเดิมจะถูกแทนที่ด้วยข้อยกเว้นใหม่ เพื่อจัดการกับกรณีเหล่านี้เราสามารถเพิ่มexcept
ประโยคทั่วไปในเนื้อความของwith
เพื่อเก็บข้อยกเว้นที่อาจเกิดขึ้นซึ่งจะมีการยกเว้นที่ไม่มีใครสังเกตเห็นได้และเปรียบเทียบกับสิ่งที่จับได้ในด้านนอกสุดในexcept
ภายหลัง - ถ้าพวกมันเหมือนกันBLOCK
หรือเป็นอย่างอื่น__exit__
(ในกรณีที่__exit__
ไม่แสดงข้อยกเว้นด้วยการส่งคืนค่าจริงที่อยู่นอกสุดexcept
จะไม่ถูกเรียกใช้งาน)
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)
วิธีการทางเลือกโดยใช้แบบฟอร์มที่เทียบเท่าที่กล่าวถึงใน PEP 343
PEP 343 - คำสั่ง "with"ระบุเวอร์ชันของwith
คำสั่ง"non-with" ที่เทียบเท่า ที่นี่เราสามารถห่อชิ้นส่วนต่าง ๆ ได้อย่างง่ายดายด้วยtry ... except
และทำให้แยกแยะระหว่างแหล่งที่มาของข้อผิดพลาดที่ต่างกัน:
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)
โดยปกติแล้ววิธีการที่เรียบง่ายกว่าจะทำได้ดี
ความต้องการในการจัดการข้อยกเว้นพิเศษนั้นค่อนข้างหายากและโดยปกติแล้วการห่อหุ้มทั้งหมดwith
ในtry ... except
บล็อกจะเพียงพอ โดยเฉพาะอย่างยิ่งถ้าแหล่งที่มาของข้อผิดพลาดต่าง ๆ ถูกระบุโดยประเภทข้อยกเว้นที่แตกต่างกัน (กำหนดเอง) (ผู้จัดการบริบทต้องได้รับการออกแบบตามลำดับ) เราสามารถแยกแยะความแตกต่างระหว่างพวกเขาได้อย่างง่ายดาย ตัวอย่างเช่น:
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
คำสั่งไม่ได้อย่างน่าอัศจรรย์ทำลายรอบtry...except
คำสั่ง