Guidelines for Python Exceptions

Code with no exceptions

Code that doesn’t use exceptions is always checking if it’s OK to do something

It’s easier to ask forgiveness than it is to get permission. - Grace Hopper

def print_object(some_object):
    # Check if the object is printable...
    if isinstance(some_object, str):
        print(some_object)
    elif isinstance(some_object, dict):
        print(some_object)
    elif isinstance(some_object, list):
        print(some_object)
    # 97 elifs later...
    else:
        print("unprintable object")

can be simplified to:

def print_object(some_object):
    # Check if the object is printable...
    try:
        printable = str(some_object)
        print(printable)
    except TypeError:
        print("unprintable object")

Choosing an exception

Choose the one that matches most closely from the exception hierachy

Intrinsic Checks

Never duplicate error checks that python does intrinsically for you

The try, except else

try:
    this()
except TypeError:
    print('problem')
else:
    no_exception()
finally:
    clean_up()

else is run when there is no exception in the try

Narrow First

Matching is beginning to end, a more general clause will stop other exceptions so best to keep the narrowest expection first.

Base raise

When raise is used in excpetion handling code it bubbled the exception up. For example when we want to keep a record of the crash but have no inetention of handling it.

Don’t raise generic Exception

More specific catches will not catch it and a more specific raise beforehand will be caught in the catch of Exception

Idiomatic Python

Idiomatic Python is written in the EAFP (Easier to Ask for Forgiveness than Permission) style (where reasonable). We can do so because exceptions are cheap in Python.

Best Practices

Never use a bare except: clause or you’ll end up suppressing real errors you didn’t intend to catch

Take advantage of Python built-ins and standard library modules that already throw exceptions

Merely logging and moving on

logger = logging.getLogger(__name__)

try:
    do_something_in_app_that_breaks_easily()
except AppError as error:
    logger.error(error)
    raise

By using the base raise it simply bubbles up the exception as opposed to returning a new stack trace if you specify the error

Unhandled

If the exception is left unhandled, the default behavior is for the interpreter to print a full traceback and the error message included in the exception.

It is better to print a more user-friendly version of the error

except Exception as err: sys.stderr.write(‘ERROR: {}’.format(str(err))) return 1

Sources