提问人:calestyo 提问时间:7/14/2023 最后编辑:Karl Knechtelcalestyo 更新时间:7/15/2023 访问量:94
如何最好地过滤其__cause__(或__context__)上的异常?
how to best filter exceptions on their __cause__ (or __context__)?
问:
给定基本异常类型:
class MyModuleError(Exception):
pass
假设我们有代码使用异常链显式地引发它:
def foo():
try:
#some code
except (ZeroDivisionError, OSError) as e:
raise MyModuleError from e
现在,在调用代码中...
try:
foo()
except MyModuleError as e:
# Now what?
我怎样才能习惯地编写子句,以便异常处理依赖于(链式异常)?except
__cause__
我想到了这些方法:
a) 使用类似:type(e)
# filter here
t=type(e.__cause__)
if t is ZeroDivisionError:
doStuff()
elif t is OSError:
doOtherStuff()
else:
raise
b) 使用类似:isinstance()
# filter here
if isinstance(e.__cause__, ZeroDivisionError):
doStuff()
elif isinstance(e.__cause__, OSError):
doOtherStuff()
else:
raise
c) 再养,如:
# filter here
try:
raise e.__cause__
except ZeroDivisionError:
doStuff()
except OSError:
doOtherStuff()
except:
raise e #which should be the "outer" exception
答:
至少我问题的性能部分可以很容易地回答。
假设以下示例代码:
#!/usr/bin/python3
import timeit
import argparse
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("-n", "--iterations", type=int, default=1)
args = parser.parse_args()
class MyModuleError(Exception):
pass
def foo():
try:
f = 1/0
except (ValueError, ZeroDivisionError) as e:
raise MyModuleError from e
def wrapper1():
try:
foo()
except MyModuleError as e:
t = type(e.__cause__)
if t is ZeroDivisionError:
pass
elif t is OSError:
pass
else:
raise
def wrapper2():
try:
foo()
except MyModuleError as e:
if isinstance(e.__cause__, ZeroDivisionError):
pass
elif isinstance(e.__cause__, OSError):
pass
else:
raise
def wrapper3():
try:
foo()
except MyModuleError as e:
try:
raise e.__cause__
except ZeroDivisionError:
pass
except OSError:
pass
except:
raise e #which should be the "outer" exception
t = timeit.timeit(wrapper1, number=args.iterations)
print(f"wrapper1: {t}")
t = timeit.timeit(wrapper2, number=args.iterations)
print(f"wrapper2: {t}")
t = timeit.timeit(wrapper3, number=args.iterations)
print(f"wrapper3: {t}")
从 CPython 3.11.4 开始,给出以下结果:
$ ./chained-exception-handling.py -n 10000000
wrapper1: 4.373930287983967
wrapper2: 4.534742605988868
wrapper3: 7.319078870001249
其实,我有点失望......我认为重新引发内部异常的方法可能是最“pythonic”的方法,但这也是最慢的(会,有点适合 Python,不是吗?!O;-) ).
评论
wrapper3
-n
e
timeit.timeit
gc
timeit.timeit
wrapper3()
'gc.enable()'
setup
不建议重新饲养。一般来说,故意举起一些东西,以便立即抓住它不是惯用的1.它的性能也较低(当实际引发异常时,异常处理可能会涉及相当多的开销),并在异常对象和当前堆栈帧之间创建一个引用循环,在 Python 的参考实现中,当辅助垃圾回收器被禁用时,这将泄漏内存。(这就是为什么在 3.x 中,在 except
块之后显式删除使用 as
创建的异常名称的原因。
在一般情况下,是类型检查的首选方法,而不是直接比较结果,因为会自动考虑子类型。用于子分型的帐户的正常功能(例如 会抓住一个,这通常是可取的);按理说,在任何正常情况下,链式异常的类型检查也应该这样做。isinstance
type
isinstance
except
except IOError:
FileNotFoundError
没有明确的内置功能;因此,这里建议使用方法 b)。
1是的,for
循环是使用 StopIteration
在内部以这种方式实现的。这是一个实现细节,用户代码看起来不像是在做这样的事情。
评论
wrapper3
-n 100000000
issubclass(PermissionError, OSError)
isinstance
__subclasshook__