A while back I wrote an entry about how modules should never raise core Python exceptions . Recently via my Referer logs I found out that some people aren't convinced by my entry , so I feel like taking another run at this topic, this time approaching it from the perspective of someone using your module.
If I'm invoking some function or method from your module and want to trap errors, I need to write code like this:
import yourmoddef fred(): try: res = yourmod.some_thing(10, 20) except SOMETHING as e: ...
In order to fill in that
SOMETHING
with the right exception, I need to consult your module's documentation. Given that I have to look this up, reusing a general exception saves me essentially nothing; at most I type a little less, and
yourmod.Error
versus
RuntimeError
is not exactly a compelling savings. If I just want to catch your explicitly raised errors, using
RuntimeError
(or a subclass of it) is not saving me any real effort.
In practice, only catching explicitly raised errors is almost always what people using your module want to do, because of the various dangers of over-broad tries that I mentioned in my original entry and elsewhere . And if I really do want to catch all errors that come out of your code, I can already do that explicitly:
try: res = yourmod.some_thing(10, 20) except Exception as e: ...
Notice that raising
RuntimeError
instead of your own error doesn't actually help me here. If I want to catch all possible errors that can happen during your module's execution, I need to go much broader than merely
RuntimeError
.
(There are valid cases for doing this broad catching, generally in top-level code that wants to insure that no uncaught exceptions ever surface to the user.)
Which brings me around to the one case where it is sensible to raise standard errors, which is when you're writing code that stands in for standard Python code that raises these errors . This is the one case where using a standard error saves me from looking things up; in fact, using a standard error is generally essential. If you're writing a class that will get used instead of a standard dictionary, raising
KeyError
and so on is absolutely essential, because that makes your objects transparent substitutes for real dictionaries.
(This is in a sense a matter of API compatibility, where the exceptions that get raised are part of the API. Sometimes this can be explicit, as in the case of
KeyError
, but sometimes this is more implicit, in cases where raising and catching errors is more uncommon.)
Sidebar: my view on subclassing
RuntimeError
I don't think you should do this because I don't think it's useful. If someone is catching
RuntimeError
today they probably intend to catch significant internal issues inside Python, not errors that your module happens to raise. Sweeping your module's errors into their
except
clauses is probably not helpful and may even be harmful.
For better or worse, I think that there is (or should be) a strong separation between 'internal' Python errors raised by the CPython interpreter and core Python modules, and module-specific errors raised by modules (yours, third party modules, and non-core modules in the standard library). These two sorts of errors don't really live in the same taxonomy and so putting one under the other is generally not going to help anyone.