明确定义的自定义异常通常比内置异常更具信息性;例如比AgeError
更多ValueError
。所以一般来说,我尽可能地使用前者。但是由于这个原因,我的代码中充斥着许多raise foo from bar
样板,只是为了传播自定义异常。这是我的意思的一个例子。如果不使用自定义例外,我只需写下:
class Person:
def set_age(self, age_as_string):
self.age = int(age_as_string)
这可能会引发TypeError
或ValueError
,但由于来电者会对其进行处理,因此单行即可。
但是要使用自定义异常,我需要样板:
class AgeError(Exception):
pass
class Person:
def set_age(self, age_as_string):
try:
self.age = int(age_as_string)
except (TypeError, ValueError) as e:
raise AgeError from e
从来电者的角度来看,这可以提供更多信息,但代价会增加300%(仅计算方法主体)并模糊set_age
的主要业务。
有没有办法让两全其美?我尝试使用谷歌搜索解决方案,但即使这个问题似乎也没有多少讨论过。我最终得到的解决方案是使用一个异常传播的装饰器,这要归功于精彩的contextlib
(如果你需要手工实现它,那么只需要一点点琐碎):
from contextlib import contextmanager
@contextmananer
def raise_from(to_catch, to_raise):
try:
yield
except to_catch as e:
raise to_raise from e
现在我只需要一个额外的行,它不会掩盖业务逻辑,甚至使错误处理逻辑更加明显(而且它看起来很漂亮):
class Person:
@raise_from(to_catch=(TypeError, ValueError), to_raise=AgeError)
def set_age(self, age_as_string):
self.age = int(age_as_string)
所以我对这个解决方案非常满意。但是,由于这样的简单解决方案仍然不存在任何未解决的问题,我担心我可能会遗漏某些东西。使用raise_from
装饰器是否有缺点我还没有考虑过?或者是否需要减少raise foo from bar
样板,表明我做错了什么?