5

Python has many strong conventions but I'm unclear on how best to manage exceptions for my module. I know it's generally good practice to define a custom exception for your module. E.g.:

class MyError(Exception):
    pass

However, what I'm not clear on is how specific exception classes should get? Should each code path that results in a failure have its own exception class? Or is it better to use only a few? A hierarchy of exceptions where each "leaf" exception inherits from a slightly more generic parent exception would allow the module's users to decide how fine-grained their exception handling is but I can see how that could become convoluted to deal with.

I know some modules use error codes to distinguish between different errors that use the same class, which is used something like this:

try:
    mymodule.do_something()
except mymodule.MyError as error:
    if error.code == mymodule.SPECIFIC_ERROR_CODE:
        handle_error()
    else:
        raise

But that feels wrong to me unless you're purposefully mimicking another library.

Is there an ideal way to handle this? Or is this question too dependent on the situation for a generic best practice?

3

2 Answers 2

7

If the library is only used by your own code or in your own organization, then YAGNI applies. You should only make only as many exception sub-classes as you need to handle separately. If two different error conditions are handled in the same way (which may include not handling but termination the application!), then you only need a single exception class.

In other words, only create as many subclasses as you actually need.

I don't really like the idea of error codes. It just introduces an additional way to signal errors which is not supported as nicely by the language (as your code example shows). If you actually need to handle the different error codes differently you might as well make separate exception classes.

4

Exception classes should be as specific as error handling might need to be.

Think about using your code: when each error is raised, how would you want to handle it?

  • Errors that must be handled the same way should be the same class.
  • Errors that you don't see a good reason to handle separately can be the same class until you find a reason.
  • Errors that a user might sometimes have good reason to distinguish should be distinct classes.
    • If one error is a special case of a more general error that you have, make the former a subclass of the latter.
    • If they're just different errors, don't have a subclass relationship between them.

PEP-8 recommends the same thing:

Design exception hierarchies based on the distinctions that code catching the exceptions is likely to need, rather than the locations where the exceptions are raised. Aim to answer the question “What went wrong?” programmatically, rather than only stating that “A problem occurred” (see PEP 3151 for an example of this lesson being learned for the builtin exception hierarchy)

Error codes are at best a low-level implementation detail leaking into your abstractions. Python is a language where is both beneficial and equally easy to just express each error as a distinct type. So error codes are only justified when you are wrapping a third-party library which already has error codes, and you can't just easily provide a good error class hierarchy over the error codes to better represent the semantics of the errors.

1
  • +1 for emphasising the /handling/ of the error/exception.
    – Phill W.
    Jun 10, 2022 at 8:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.