Python 3

home

Introduction to Python

davidbpython.com




Exception Trapping

exception trapping: handling errors after they occur

Introduction: unanticipated vs. anticipated errors


Think of errors as being of two general kinds -- unanticipated and anticipated:


Exampls of anticipated errors:





KeyError: when a dictionary key cannot be found.

If the user enters a key that is not in the dict, we can expect this error.


mydict = {'1972': 3.08, '1973': 1.01, '1974': -1.09}

uin = input('please enter a year: ')         # user enters 2116

print(f'mktrf for {uin} is {mydict[uin]}')

  #  Traceback (most recent call last):
  #    File "/Users/david/test.py", line 5, in <module>
  #      print(f'mktrf for {uin} is {mydict[uin]}')
  #                                  ~~~~~~^^^^^
  #  KeyError: '9999'




ValueError: when the wrong value is used with a function or statement.

If we ask the user for a number, but anticipate they might not give us one.


uin = input('please enter an integer:  ')

intval = int(uin)                           # user enters 'hello'

print('{uin} doubled is {intval*2}')

  #  Traceback (most recent call last):
  #    File "/Users/david/test.py", line 3, in <module>
  #      intval = int(uin)                           # user enters 'hello'
  #               ^^^^^^^^
  #  ValueError: invalid literal for int() with base 10: 'hello'




FileNotFoundError: when a file can't be found.

If we attempt to open a file but it has been moved or deleted.


filename = 'thisfile.txt'

fh = open(filename)

  #  Traceback (most recent call last):
  #    File "/Users/david/test.py", line 3, in <module>
  #      fh = open(filename)
  #           ^^^^^^^^^^^^^^
  #  FileNotFoundError: [Errno 2] No such file or directory: 'thisfile.txt'




handling errors approach: "asking for permission"

Up to now we have managed anticipated errors by testing to make sure an action will be succesful.


Examples of testing for anticipated errors:


So far we have been dealing with anticipated errors by checking first -- for example, using .isdigit() to make sure a user's input is all digits before converting to int().
However, there is an alternative to "asking for permission": begging for forgiveness.





handling errors approach: "begging for forgiveness"

the try block and except block


try:
    uin = input('please enter an integer:  ')   # user enters 'hello'
    intval = int(uin)                           # int() raises a ValueError
                                                # ('hello' is not a valid value)

    print('{uin} doubled is {intval*2}')

except ValueError:
    exit('sorry, I needed an int')   # the except block cancels the
                                     # ValueError and takes action





the procedure for setting up exception handling

It's important to witness the exception and where it occurs before attempting to trap it.


It's strongly recommended that you follow a specific procedure in order to trap an exception:

  1. allow the exception to occur
  2. note the exception type and line number where it occurs
  3. wrap the line that caused the error in a try: block
  4. wrap statements you would like to be executed if the error occurs in an except: block
  5. test that when the exception is raised, the except block is executed
  6. test that when the exception is not raised, the except block is not executed




  7. trapping multiple exceptions

    Multiple exceptions can be trapped using a tuple of exception types.


    companies = ['Alpha', 'Beta', 'Gamma']
    
    user_index = input('please enter a ranking:  ')   # user enters '4' or 'hello'
    
    try:
        list_idx = int(user_index) - 1
    
        print(f'company at ranking {user_index} is {companies[list_idx]}')
    
    except (ValueError, IndexError):
        exit(f'max index is {len(companies) - 1}')
    

    Here we trap two anticipated errors: if the user types a non-number and a ValueError exception is raised, or an invalid list index and an IndexError is raised, the except: block will be executed.





    chaining except: blocks

    The same try: block can be followed by multiple except: blocks, which we can use to specialize our response to the exception type.


    companies = ['Alpha', 'Beta', 'Gamma']
    
    user_index = input('please enter a ranking:  ')   # user enters '4'
    
    try:
        list_idx = int(user_index) - 1
    
        print(f'company at ranking {user_index} is {companies[list_idx]}')
    
    except ValueError:
        exit('please enter a numeric ranking')
    
    except IndexError:
        exit(f'max index is {len(companies) - 1}')
    

    The exception raised will be matched against each type, and the first one found will excecute its block.





    avoiding except: and except exception:

    When we don't specify an exception, Python will trap any exception. This is a bad practice.


    ui = input('please enter a number: ')
    
    try:
        fval = float(ui)
    except:                  # AVOID!!  Should be 'except ValueError:'
        exit('please enter a number - thank you')
    

    However, this is a bad practice. Why?

    1. except: or except Exception: can trap any type of error, so an unexpected error could go undetected
    2. except: or except Exception: does not specify which type of exception was expected, so it is less clear to the reader


    There are certain limited circumstances under which we might use except: by itself, or except Exception. These might include wrapping the whole program execution in a try: block and trapping any exception that is raised so the error can be logged and the program doesn't need to exit as a result.





    [pr]