Exceptions CS 112 @ GMU
Exceptions When an unrecoverable action takes place, normal control flow is abandoned: an exception value crashes outwards until caught. various types of exception values can be raised: ValueError, IndexError, KeyError, etc. we can selectively catch particular types, and recover normal control flow
quick example: try-except blocks n = int(input("how many inches?")) (feet, inches) = (n//12, n%12) print("that's %d feet, %d inches" % (feet, inches)) except: print("oops, that wasn't a number!") print("goodbye.") ex1.py Python attempts the try-block. If it succeeds (no exceptions raised), we skip the except-block and move on (print 'goodbye.') if any exception is raised, we immediately abort the entire try block and jump to the except-block (it catches all kinds of exceptions). The except block is run, and then fall out to print 'goodbye.'
immediate escaping if an exception is raised, we don't complete that expression, we don't complete that statement. be careful that might mean a variable doesn't exist, even after the try-except code! ex2.py choice = int(input("how much?")) print ("choice received.") except: print("oops.") # this might fail! choice might not exist. print(choice) Consider these inputs: 10 3.5 hello
Some Common Exceptions Exception Type FileNotFoundError IndexError KeyError NameError SyntaxError TypeError ValueError ZeroDivisionError Description tried to open a non-existent file tried to index into a structure with a not-present index. tried to access non-existent key in a dictionary. identifier for a name couldn't be found in scope. syntax error encountered. type error encountered, e.g. argument to built-in is of wrong type. built-in function/operation received value of right type, but wrong value (e.g., int() received a str, but it didn't represent a number) tried to divide by zero. 5
Exceptions Hierarchy (excerpt) There are many exception classes organized into a hierarchy each name here is its own python type! indentations: indicates inheritance (parent/child relationships). A KeyError is a more specific kind of LookupError; it's allowed anywhere a LookupError is. abbreviated version of the hierarchy BaseException +-- KeyboardInterrupt +-- Exception +-- ArithmeticError +-- ZeroDivisionError +-- EnvironmentError +-- OSError +-- FileNotFountError +-- EOFError +-- LookupError +-- IndexError +-- KeyError +-- NameError +-- SyntaxError +-- SystemError +-- TypeError +-- ValueError (found at https://docs.python.org/3/library/exceptions.html#exception-hierarchy )
variations: multiple except blocks, multiple types per block import sys filename = sys.argv[1] f = open(filename) lines = f.readlines() f.close() xs = [] for line in lines: # file might not exist xs. append ( int(line) )# might not be an int. secret = xs[3] / xs[10] print("secret result:",secret) except FileNotFoundError: print("file didn't exist.") except (ValueError, IndexError) as e: print("bad input!", type(e), str(e)) # index might not exist except Exception as anyname: # any zero-division?? print("catch-all: unforseen! ", str(anyname)) print("end of example.") ex3.py Notes when exception occurs, only the first compatible except-block runs! Example details sys.argv lets us access the commandline arguments except block for FileNotFoundError didn't want to inspect the exception value; no as e clause needed. ValueError, IndexError, and any child classes of exceptions are all handled here. wanted to inspect the object, so as <name> clause included. except Exception block handles exceptions of type Exception and any child classes that's all exceptions!
variations we can inspect the exception value if desired: except SomeType as anyname: statements <can use anyname> we can ignore the particular value and still catch those types by skipping the as-clause: except SomeType: statements
variations we can have multiple except blocks. first block to handle the actual type of raised exception is the only one to run "parent" types of exceptions match all child types (the deeper indentations of that chart are child types) except Exception thus catches anything we can catch anything, and ignore the particular value, with a raw except: block
caution things can still crash any raised exception whose type isn't compatible with any of the except blocks will "escape": it crashes further, out of the next layer of try-blocks, function calls, until it either is caught elsewhere or crashes the entire program. xs = [5,10,15,20] ex4.py index = int(input("which spot? ")) val = xs[index] print("you chose "+str(val)) except ValueError: print("that wasn't an int.") print("end of program.") Sample Inputs: 2 # successful three # ValueError caught 39 # IndexError escapes!
POLL 8A basic try-except usage
Validating Input Loop continues to execute, raising and handling exceptions, until user complies. need_input = True ex5.py while need_input: n = int(input("#items: ")) fr = int(input("#friends: ")) each = n/fr need_input = False except Exception as e: print(e) print("everyone gets %s items." % each) sample calls demo$ python3 ex5.py #items: asdf invalid literal for int() with base 10: 'asdf' #items: 3 #friends: 0 division by zero #items: 10 #friends: 5 everyone gets 2.0 items.
validating input: alternate version We can use while True: and break with exceptions for a convenient way to escape: if any exceptions occur, we skip the break and the loop forces us to try again. need_input = True ex5.py while need_input: n = int(input("#items: ")) fr = int(input("#friends: ")) each = n/fr need_input = False except Exception as e: print(e) print("everyone gets %s items." % each) while True: ex5_alt.py n = int(input("#items: ")) fr = int(input("#friends: ")) each = n/fr break except Exception as e: print(e) print("everyone gets %s items." % each)
Practice Problem What happens if we instead had the while loop inside the try block, like this? practice1.py need_input = True while need_input: x = int(input("#: ")) need_input = False except Exception as e: print(e) print ("successfully got x: "+str(x))
Practice Problems Using exception handling (try-except): successfully get a positive integer from the user. write a function that accepts a message string, a list of acceptable int values. It then forces the user to supply one of those values, which is returned.
POLL 8B multiple exception blocks
try-except-else the else block only runs if no exceptions were raised in the tryblock (no except block ran) ex6.py v = int(input("? ")) d = {1:'a', 2:'b', 3:'c'} k = 12//v print(d[k]) except (ValueError, ZeroDivisionError) as e: print(e) else: print(" ") 17 sample behavior: demo$ python3 ex6.py? 12 a demo$ python3 ex6.py? asdf invalid literal for int() with base 10: 'asdf' demo$ python3 ex6.py? 3 Traceback (most recent call last): File "ex6.py", line 4, in <module> print(d[v]) KeyError: 4 demo$
try-except(s)-(else)-finally a finally-block always runs. - after successful try (and else) blocks. - after except-block - before uncaught exception keeps crashing ex7.py print(50 / (int(input("? ")))) except ZeroDivisionError as e: print(e) else: print("success!") finally: print("always runs.") 18 sample behavior demo$ python3 ex7.py? 5 10.0 success! always runs. demo$ python3 ex7.py? 0 division by zero always runs. demo$ python3 ex7.py? asdf always runs. Traceback [truncated] ValueError: invalid literal
a bit of planned escaping we might write code that lets some types of exception escape whoever calls this code will be responsible for catching them def get_count(): ex8.py f = open("logfile.txt") return int(f.read()) except FileNotFoundError: return 100 def main(): n = get_count() except ValueError: n = 0 print("n =", n) main() Sample Usage when logfile.txt doesn't exist: get_count catches the FileNotFoundError prints n = 100 when logfile.txt contains "23": prints n = 23 when logfile.txt contains "hello": main catches the ValueError, prints n = 0 19
Practice Problem Write a function that accepts a file name, opens the file, and reads out a single integer from it. Handle a FileNotFoundError by returning -1, and let ValueErrors escape (don't handle them) show how to call the program and cause both types of errors, including what would be in the file when it is present.
Practice Problem Write a function that examines a list and checks if there are k values in a row that are identical. Rather than carefully stop exactly when necessary, try letting an index error be caught and ignored. "try more than enough, ignore the bad cases." def k_twins(xs, k):
POLL 8C extra exception features
Raising Exceptions We can generate an exception on purpose (and hopefully catch it somewhere else!) performed with a raise statement, which needs an expression of some Exception type. This usually means calling a constructor ( init method). Examples: raise Exception("boo!") raise ArithmeticError ("this doesn't add up!") raise ValueError("needed a positive number") except IOError as e: print ("catching it, re-raising it.") raise e
Raising Exceptions Reusing/raising a few specific exception types is useful: Exception for general issues (but a bit vague) TypeError, when the supplied arg. was the wrong type ValueError when your code only should be run on some batch of values and the wrong one was given(e.g., only works on positive integers) Any type can be used if it suits your purpose; just call the constructor (see examples on previous slide) Then you can raise them and catch them as before. you can also make your own brand-new types of exceptions!
Re-raising the Same Exception We can directly re-raise the caught exception object. No need to construct exception value; we already have one. def get_input(): ex9.py return float (input("#: ")) except ValueError as e: print("in get_input: "+str(e)) raise ValueError("ERR") def main(): print( get_input()) except ( ValueError) as e: print ("in main: "+str(e)) main() sample usage demo$ python3 ex9.py note the quotes! #: 'a' in get_input: could not convert string to float: 'a' in main: ERR 25
Practice Problem: Scenario A bridge inspector, Jon, is doing the yearly inspection for some bridge. He has a can of paint where he can touch up old paint, and he also has a "bridge closed" sign with him. He can also report to his supervisor. Jon sees a big rusty spot. He can: paint over it (is this 'solution' good enough?) quit inspecting and tell his supervisor put up the "bridge closed" sign, but also tell his supervisor. How do Jon's choices sound like exception handling approaches?
POLL 8D raising exceptions
User-Defined Exceptions (sneak peek at classes) We can create our own types of exceptions. They can be raised, propagated, and caught just like any other exception types. Just include Exception as the parent class as shown below (put Exception in parentheses after classname). We'll explore this more after learning classes. class BadInput(Exception): def init (self, value): self.value = value ex10.py 28
User-Defined Exceptions (sneak peek at classes) raise keyword used with a call to exception's constructor except-block used with our new type of exception from ex10 import BadInput ex11.py x = int(input("#? ")) if x==13: raise BadInput("that's unlucky!") print(x*10) except BadInput as e: print("uhoh: "+e.value) except Exception as e: print(e) sample usage demo$ python3 ex11.py #? 5 50 demo$ python3 ex11.py #? 13 uhoh: that's unlucky! demo$ python3 ex11.py #? asdf invalid literal for int() with base 10: 'asdf' 29