Well-defined custom exceptions are often more informative than builtin ones; e.g. AgeError
more so than ValueError
. So in general I try to use the former when I can. But as a consequence of this, my code is littered with lots of raise foo from bar
boilerplate just to propagate a custom exception. Here is an example of what I mean. Without using custom exceptions, I'd just write:
class Person:
def set_age(self, age_as_string):
self.age = int(age_as_string)
This may raise either TypeError
or ValueError
, but since the caller will handle it, a one-liner is just fine.
But to use custom exceptions, I need boilerplate:
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
This is more informative from the caller's point of view, but costs 300% more code (just counting the method body) and obscures the main business of set_age
.
Is there a way to have the best of both worlds? I tried googling for solutions but even the problem doesn't seem to be much discussed at all. The solution I eventually come to is use an exception-propagating decorator, which is trivial to write thanks to the wonderful contextlib
(and only a little less trivial if you need to implement it by hand):
from contextlib import contextmanager
@contextmananer
def raise_from(to_catch, to_raise):
try:
yield
except to_catch as e:
raise to_raise from e
Now I need only one-extra line, which doesn't obscure the business logic, and even makes the error-handling logic somewhat more obvious (and it's smart-looking):
class Person:
@raise_from(to_catch=(TypeError, ValueError), to_raise=AgeError)
def set_age(self, age_as_string):
self.age = int(age_as_string)
So I'm quite happy with this solution. But since it's unlikely that there still exists any unsolved problem with an easy solution like this, I'm worried that I may be missing something. Are there disadvantages with using the raise_from
decorator that I haven't considered? Or is the very need to reduce the raise foo from bar
boilerplate an indication that I'm doing something wrong?