Bileşik bir withifadeden kaynaklanan istisnaların olası kökenleri arasında ayrım yapma
Bir withifadede ortaya çıkan istisnalar arasında ayrım yapmak zordur, çünkü farklı yerlerde ortaya çıkabilirler. İstisnalar aşağıdaki yerlerden birinden (veya burada çağrılan işlevlerden) kaynaklanabilir:
ContextManager.__init__
ContextManager.__enter__
- gövdesi
with
ContextManager.__exit__
Daha fazla ayrıntı için Context Manager Türleri ile ilgili belgelere bakın .
Biz sadece sarma, bu farklı durumu ayırt etmek istiyorsanız withbir içine try .. exceptyeterli değildir. Aşağıdaki örneği düşünün (örnek ValueErrorolarak kullanmak , ancak tabii ki başka bir istisna türüyle değiştirilebilir):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
Burada exceptdört farklı yerin hepsinden kaynaklanan istisnaları yakalayacak ve böylece aralarında ayrım yapmaya izin vermeyecektir. Dışarı bağlam yöneticisi nesne örneğinin taşırsanız with, biz ayırt edebilir __init__ve 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
Etkili bir şekilde bu sadece __init__parçaya yardımcı oldu, ancak withyürütmeye başlayan gövdenin (yani __enter__ve diğerleri arasında ayrım yapma ) olup olmadığını kontrol etmek için ekstra bir sentinel değişkeni ekleyebiliriz :
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
Zor kısım, ortaya çıkan istisnalar arasında ayrım yapmaktır BLOCKve __exit__çünkü nasıl ele alınacağına karar verebilecek withiradenin gövdesinden kaçan bir istisna geçirilir __exit__( dokümanlara bakın ). Ancak __exit__kendini yükseltirse, orijinal istisna yenisiyle değiştirilecektir. Bu davalarla başa çıkmak için, fark edilmeden kaçabilecek herhangi bir potansiyel istisnayı depolamak ve daha sonra en dışta yakalananla karşılaştırmak exceptiçin - vücudun genel bir cümlesini ekleyebiliriz - eğer aynıysa, menşe ya da aksi halde ( en dıştaki gerçek değeri döndürerek istisnayı bastırması durumunda)withexceptBLOCK__exit____exit__except basitçe yürütülmez).
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'te belirtilen eşdeğer formu kullanarak alternatif yaklaşım
PEP 343 - "with" Deyimi , withdeyimin eşdeğer bir "birlikte olmayan" sürümünü belirtir . Burada çeşitli parçaları kolayca try ... exceptfarklı potansiyel hata kaynaklarıyla sarabilir ve böylece birbirinden ayırabiliriz :
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)
Genellikle daha basit bir yaklaşım iyi olur
Bu tür özel istisna muamelesine duyulan ihtiyaç oldukça nadir olmalı ve normalde withbir try ... exceptbloğa normal olarak sarılması yeterli olacaktır. Özellikle çeşitli hata kaynakları farklı (özel) istisna türleriyle (içerik yöneticilerinin buna göre tasarlanması gerekir) belirtilirse, bunlar arasında kolayca ayrım yapabiliriz. Örneğin:
try:
with ContextManager():
BLOCK
except InitError: # raised from __init__
...
except AcquireResourceError: # raised from __enter__
...
except ValueError: # raised from BLOCK
...
except ReleaseResourceError: # raised from __exit__
...
withifade, çevredeki birtry...exceptifadeyi sihirli bir şekilde bozmaz .