Exception Handling

Why Exceptions?

Deal:

  • Return < 0 on error

  • Caller has to check

  • Caller has to pass error on

def do_much(this, that):
    if do_this(this) < 0:
        return -1
    if do_that(that) < 0:
        return -1
    return 0

def do_this(this):
    if this == 2:
        return -1
    else:
        return 9

def do_that(that):
    if that == 5:
        return -1
    else:
        return 'blah'

Exception Handling

Plan is: write less code ⟹ cleaner code

def do_much(this, that):
    do_this(this)
    do_that(that)

try:
    do_much(1, 5)
except MyError as e:
    print('Error:', e.msg,
          file=sys.stderr)

def do_this(this):
    if this == 2:
        raise MyError('this is 2')
    else:
        return 9

def do_that(that):
    if that == 5:
        raise MyError('that is 5')
    else:
        return 'blah'

Exceptions

Exceptions are objects

  • Python 2: can be anything

  • Python 3: must be derived from class BaseException

    • User defined exception should be derived from Exception

  • Object oriented programming

class MyError(Exception):
    def __init__(self, msg):
        self.msg = msg

Catching All Exceptions

a_dict = {}
try:
    print(a_dict['novalidkey'])
except:   # KeyError
    print("d'oh!")
  • Catches everything no matter what

  • Hides severe programming errors

  • ⟶ use only if you really know you want

try:
    print(nonexisting_name)
except:   # NameError
    print("d'oh!")

Catching Exceptions By Type

a_dict = {}
try:
    print(a_dict['novalidkey'])
except KeyError:
    print("d'oh!")
  • NameError (and most others) passes through

    • … and terminate the program unless caught higher in the call chain

  • Very specific ⟶ best used punctually

Catching Exceptions By Multiple Types

a_dict = {}
try:
    print(a_dict[int('aaa')])
except (KeyError, ValueError):
    print("d'oh!")
  • (Btw, the exception list is an iterable of type objects)

  • As always: reflect your intentions

  • Is the handling the same in both cases?

    • I’d say very rarely

Storing the Exception’s Value

  • Many exceptions’ only information is their type

  • ⟶ “A KeyError happened!”

  • Sometimes exceptions carry additional information

class MyError(Exception):
    def __init__(self, msg):
        self.msg = msg

def do_something():
    raise MyError('it failed')

try:
    do_something()
except MyError as e:
    print(e.msg)

Order of Except-Clauses (1)

  • Except-Clauses are processed top-down

  • ⟶ Very important when exceptions are related/inherited

  • MyError is a Exception

class MyError(Exception):
    def __init__(self, msg):
        self.msg = msg

def do_something():
    raise MyError('it failed')

Order of Except-Clauses (2)

Wrong
try:
    do_something()
except Exception as e:
    print('unexpected')
except MyError as e:
    print(e.msg)
  • MyError is a Exception

  • ⟶ eats all MyError objects

  • MyError never caught

Right
try:
    do_something()
except MyError as e:
    print(e.msg)
except Exception as e:
    print('unexpected')

Rule:

  • Catch the most specific exception first