Python 3

home

Introduction to Python

davidbpython.com




Exception Trapping


exception trapping: handling exceptions after they are raised

Introduction: unanticipated vs. anticipated exceptions


Think of exceptions that we see raised by Python (SyntaxError, IndexError, etc.) as being of two general kinds -- unanticipated and anticipated:


Examples of anticipated exceptions:

In each of these cases, we know the exception could be raised, and so we write code to try to avoid the exception, or to deal with it if it does.






KeyError: when a dictionary key cannot be found

If the user enters a key, but it can't be found in the dict.


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

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

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

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






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

If we ask the user for a number, but they give us something else.


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 'seven'
  #               ^^^^^^^^
  #  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'





one approach to managing exceptions: "asking for permission"

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


Examples of testing for anticipated exceptions:


So far we have been dealing with anticipated exceptions 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.






another approach to managing exceptions: "begging for forgiveness"

The try block can trap exceptions and the except block can deal with them.


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 it is raised 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 be raised
  2. note the exception type and line number where it was raised
  3. wrap the line that caused the exception in a try: block
  4. follow with an except: block, containing statements to be executed if the exception is raised
  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. Ex. 9.12 - 9.13






    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}')
    






    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. Ex. 9.14






    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 exception, so an unexpected exception 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. One comment practice is to place the entire program execution in a try: block and to trap any exception that is raised, so the exception can be logged and the program doesn't need to exit as a result.)





    [pr]