3

I'm trying to figure out if there's a more pythonic way to do a specific error handling task. I want to catch multiple specific custom exceptions and do something specific depending on which exception is caught (like use a specific custom exit code, or add a specific log message, etc...). However, I also want, if any exception is raised, to do send an email saying the script didn't complete successfully, had exit code __, the log is at C:\foo\bar.txt

I know I could include everything I want to do in each except, such as:

try:
    do_something()
except CustomError1:
    exitcode = 1
    send_error_email(exitcode)
    sys.exit(exitcode)
except CustomError2:
    exitcode = 2
    send_error_email(exitcode)
    sys.exit(exitcode)

But I'm wondering if there's a more pythonic or more efficient way of doing it. I'm imagining something like

try:
    do_something()
except CustomError1:
    exitcode = 1
except CustomError2:
    exitcode = 2
except ParrentCustomErrorClass:
    send_error_email()
    sys.exit(exitcode)

If it matters, I'm currently stuck with python 2.7, but need to be able to port solution to 3.x when 3rd party applications allow it.

2 Answers 2

3

Two idioms I have seen are:

With finally

exit_code = None
try:
   do_something()
except CustomError1:
    exit_code = 1
except CustomError2:
    exit_code = 2
finally:
    if exit_code is not None:
        send_error_mail(exit_code)

With "dictionary dispatch"

try:
    do_something()
except Exception as e:
    code = {
        CustomError1: 1
        CustomError2: 2
    }.get(type(e))
    if code is None:
        raise
    send_error_email(code)

(or if you wanna go full PEP572),

    ...
    if (code := {
        CustomError1: 1
        CustomError2: 2
    }.get(type(e))) is not None:
         send_error_email(code)
    raise

If you control your exceptions however (and they are not library exceptions) - I do like @scnerd's solution too. If you don't - and want to catch ValueErrors or TypeErrors or whatever then one of these is the way to go.

Sign up to request clarification or add additional context in comments.

2 Comments

I had a very similar idea about using a dict to store the Exceptions and was about to post it when I saw your answer. I didn't know about these recommended ways. Can you explain why it is better to define that dict again any time an exception is handled? I would have declared a public dictionary errors once containing all the exceptions as keys like you, and whenever an exception had to be handled, did something like except tuple(errors.keys()) as err: ...code = errors.get(type(err)). It would be less code but probably not as fast?
It will be just as fast - marginally different if anything. It just depends if this is a one-off kind of thing or you want to be doing this commonly - in which case your way is better :)
2

Can you just define the exit codes in the exception types themselves?

class ParentCustomErrorClass(Exception):
    exitcode = 999

    def send_error_email(self):
        raise NotImplementedError()

    def die_horribly(self):
        import sys
        self.send_error_email()
        sys.exit(self.exitcode)

class CustomError1(ParentCustomErrorClass):
    exitcode = 1

class CustomError2(ParentCustomErrorClass):
    exitcode = 2

try:
    do_something()
except ParentCustomErrorClass as ex:
    ex.die_horribly()

1 Comment

I'd never really though of adding error handling into an error class before (sounds stupid, but didn't really think of them as a normal class), so thank you for the insight and will possibly use this in future. For the current project, unfortunately, I don't have control over the error classes however as I'm catching from 3rd party modules.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.