Python 3home |
Introduction to Python
davidbpython.com
Introduction: unanticipated vs. anticipated errors
Think of errors as being of two general kinds -- unanticipated and anticipated:
Exampls of anticipated errors:
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'
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'
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'
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.
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
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:
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.
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.
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?
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.