Why use exceptions?
♦ Error handling
♦ Event notification
♦ Special-case handling
♦ Unusual control-flows
Exception topics
♦ The basics
♦ Exception idioms
♦ Exception catching modes
♦ Class exceptions
♦ Exception gotchas
♦ A high-level control flow device
♦ try statements catch exceptions
♦ raise statements trigger exceptions
♦ Exceptions are raised by Python or programs
Basic forms
● Python 2.5+: except/finally can now be mixed
● Python 2.5+: with/as context managers
● Python 3.X: “except E as X”, “raise from E”
♦ try/except/else
try:
<statements> # run/call actions
except <name>:
<statements> # if name raised during try block
except
<name>, <data>:
<statements> # if 'name' raised; get extra data
else:
<statements> # if no exception was raised
♦ try/finally
try:
<statements>
finally:
<statements> # always run 'on the way out'
♦ raise
raise <name> # manually trigger an exception
raise <name>, <data> # pass extra data to catcher too
♦ assert
assert <test>, <message>
# if not test: raise AssertionError, message
♦ with/as context managers (2.5+)
# alternative
to common try/finally idioms
from __future__ import with_statement # till 2.6
with open('/etc/passwd', 'r') as f:
# auto-closed after with
for line in
f: # even if
exception in block
print line
... more
processing code ...
lock = threading.Lock()
with lock: # auto
acquired, released
# critical section
of code # classes may define
managers
Builtin exceptions
♦ Python triggers builtin exceptions on errors
♦ Displays message at top-level if not caught
def kaboom(list, n):
print list[n] # trigger IndexError
try:
kaboom([0, 1, 2], 3)
except IndexError: # catch exception here
print 'Hello world!'
User-defined exceptions
♦ Python (and C) programs raise exceptions too
♦ User-defined exceptions are objects
MyError = "my error"
def stuff(file):
raise MyError
file = open('data', 'r') # open a file
try:
stuff(file) # raises exception
finally:
file.close() # always close file
♦ EOFError sometimes signals end-of-file
while 1:
try:
line = raw_input() # read from stdin
except EOFError:
break
else:
<process next ‘line’ here>
♦ Searches sometimes signal success by ‘raise’
Found = "Item found"
def searcher():
<raise Found or return>
try:
searcher()
except Found:
<success>
else:
<failure>
♦ Outer ‘try’ statements can be used to debug code
try:
<run program>
except: # all uncaught exceptions come here
import sys
print 'uncaught!', sys.exc_info()[:2] # type, value
♦ Try statements nest (are stacked) at runtime
♦ Python selects first clause that matches exception
♦ Try blocks can contain a variety of clauses
♦ Multiple excepts: catch 1-of-N exceptions
♦ Try can contain ‘except’ or ‘finally’, but not both
Try block clauses
Operation |
Interpretation |
except: |
catch
all exception types |
except
name: |
catch
a specific exception only |
except
name, value: |
2.X:
catch exception and its extra data |
except
name as value: |
3.X: catch exception and its instance |
except
(name1, name2): |
catch
any of the listed exceptions |
else: |
run
block if no exceptions raised |
finally: |
always
perform block, exception or not |
♦ Exceptions nest at run-time
● Runs most recent matching except clause
file: nestexc.py
def action2():
print 1 + [] # generate TypeError
def action1():
try:
action2()
except TypeError: # most recent matching try
print 'inner try'
try:
action1()
except TypeError: # here iff action1 re-raises
print 'outer try'
% python nestexc.py
inner try
♦ Catching 1-of-N exceptions
● Runs first match: top-to-bottom, left-to-right
● See manuals or reference text for a complete list
try:
action()
except NameError:
...
except IndexError
...
except KeyError:
...
except (AttributeError, TypeError, SyntaxError):
...
else:
...
♦ ‘finally’ clause executed on the way out
● useful for ‘cleanup’ actions: closing files,...
● block executed whether exception occurs or not
● Python propagates exception after block finishes
● but exception lost if finally runs a raise, return, or break
file: finally.py
def divide(x, y):
return x / y # divide-by-zero error?
def tester(y):
try:
print divide(8, y)
finally:
print 'on the way out...'
print '\nTest 1:'; tester(2)
print '\nTest 2:'; tester(0) # trigger error
% python finally.py
Test 1:
4
on the way out...
Test 2:
on the way out...
Traceback (innermost last):
File "finally.py", line 11, in ?
print 'Test 2:'; tester(0)
File "finally.py", line 6, in tester
print divide(8, y)
File "finally.py", line 2, in divide
return x / y # divide-by-zero error?
ZeroDivisionError: integer division or modulo
♦ Optional data
● Provides extra exception details
● Python passes None if no explicit data
file: helloexc.py
myException = 'Error' # string object
def raiser1():
raise myException, "hello" # raise, pass data
def raiser2():
raise myException # raise, None implied
def tryer(func):
try:
func()
except myException, extraInfo:
print 'got this:', extraInfo
% python
>>> from helloexc import *
>>> tryer(raiser1)
got this: hello
>>> tryer(raiser2)
got this: None
● Should use classes today: only option in 3.X, per BDFL
● Useful for catching categories of exceptions
● String exception match: same object (‘is’ identity)
● Class exception match: named class or subclass of it
● Class exceptions support exception hierarchies
General raise forms
raise string # matches same string object
raise string, data # optional extra data (default=None)
raise class, instance # matches class or its superclass
raise instance # = instance.__class__, instance
Example
file: classexc.py
class Super: pass
class Sub(Super): pass
def raiser1():
X = Super() # raise listed class instance
raise X
def raiser2():
X = Sub() # raise instance of subclass
raise X
for func in (raiser1, raiser2):
try:
func()
except Super: # match Super or a subclass
import sys
print 'caught:', sys.exc_info()[0]
% python classexc.py
caught: <class Super at 770580>
caught: <class Sub at 7707f0>
Example: numeric library
class NumErr: pass
class Divzero(NumErr): pass
class Oflow(NumErr): pass
raise DivZero()
import mathlib
try:
mathlib.func(…)
except mathlib.NumErr:
…report and recover…
Example: using raised instance
class MyBad:
def __init__(self, file, line):
self.file = file
self.line = line
def display(self):
print self.file * 2
def parser():
raise MyBad('spam.txt', 5)
try:
parser()
except MyBad, X:
print X.file, X.line
X.display()
# built-in file error numbers
def parser():
open('nonesuch')
try:
parser()
except IOError, X:
print X.errno, '=>', X.strerror
What to wrap in a try statement?
● Things that commonly fail: files, sockets, etc.
● Calls to large functions, not code inside the function
● Anything that shouldn’t kill your script
● Simple top-level scripts often should die on errors
● See also atexit module for shutdown time actions
Catching too much?
● Empty except clauses catch everything
● But may intercept error expected elsewhere
try:
[...]
except:
[...] # everything comes here: even sys.exit()!
Catching too little?
● Specific except clauses only catch listed exceptions
● But need to be updated if add new exceptions later
● Class exceptions would help here: category name
try:
[...]
except (myerror1, myerror2): # what if I add a myerror3?
[...] # non-errors
else:
[...] # assumed to be an error
Solution: exception protocol design
Click here to go to
lab exercises
Click here to go to
exercise solutions
Click here to go to
solution source files
Click here to go to
lecture example files