Membedakan antara kemungkinan asal-usul pengecualian yang muncul dari with
pernyataan majemuk
Membedakan antara pengecualian yang terjadi dalam with
pernyataan itu rumit karena mereka bisa berasal dari tempat yang berbeda. Pengecualian dapat diajukan dari salah satu tempat berikut (atau fungsi yang disebut di dalamnya):
ContextManager.__init__
ContextManager.__enter__
- tubuh
with
ContextManager.__exit__
Untuk lebih jelasnya lihat dokumentasi tentang Jenis-jenis Manajer Konteks .
Jika kita ingin membedakan antara kasus-kasus yang berbeda, hanya membungkus with
menjadi try .. except
tidak cukup. Pertimbangkan contoh berikut (menggunakan ValueError
sebagai contoh tetapi tentu saja itu bisa diganti dengan jenis pengecualian lainnya):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
Di sini surat except
wasiat akan menangkap pengecualian yang berasal dari keempat tempat yang berbeda dan karenanya tidak memungkinkan untuk membedakan di antara mereka. Jika kita memindahkan instantiasi objek manajer konteks di luar with
, kita dapat membedakan antara __init__
dan 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
Secara efektif ini hanya membantu __init__
bagian tetapi kita dapat menambahkan variabel sentinel tambahan untuk memeriksa apakah tubuh yang with
mulai dieksekusi (yaitu membedakan antara __enter__
dan yang lain):
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
Bagian yang sulit adalah untuk membedakan antara pengecualian yang berasal dari BLOCK
dan __exit__
karena pengecualian yang lolos dari tubuh with
akan diteruskan ke __exit__
yang dapat memutuskan bagaimana menanganinya (lihat dokumen ). Namun jika __exit__
memunculkan sendiri, pengecualian asli akan digantikan oleh yang baru. Untuk menangani kasus-kasus ini kita dapat menambahkan except
klausa umum dalam tubuh with
untuk menyimpan setiap pengecualian potensial yang seharusnya lolos tanpa diketahui dan membandingkannya dengan yang tertangkap di terluar except
nanti - jika mereka sama, ini berarti asal usulnya. BLOCK
atau sebaliknya __exit__
(dalam kasus __exit__
menekan pengecualian dengan mengembalikan nilai sebenarnya yang paling luarexcept
tidak akan dieksekusi).
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)
Pendekatan alternatif menggunakan formulir setara yang disebutkan dalam PEP 343
PEP 343 - Pernyataan "with" menentukan versi with
pernyataan "non-with" yang setara . Di sini kita dapat dengan mudah membungkus berbagai bagian dengan try ... except
dan dengan demikian membedakan antara sumber kesalahan potensial yang berbeda:
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)
Biasanya pendekatan yang lebih sederhana akan baik-baik saja
Kebutuhan untuk penanganan pengecualian khusus semacam itu harus sangat jarang dan biasanya membungkus keseluruhan with
dalam satu try ... except
blok sudah cukup. Terutama jika berbagai sumber kesalahan ditunjukkan oleh tipe pengecualian (khusus) yang berbeda (manajer konteks perlu dirancang sesuai) kita dapat dengan mudah membedakannya. Sebagai contoh:
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
pernyataan tidak ajaib istirahat sekitarnyatry...except
pernyataan.