Exceptions are errors that arise when programs are run. (This distinguishes them from mere syntax errors, which can be caught before programs are run.)
Exceptions are powerful tools that you can use to write better programs.
Python has a lot of built-in exceptions with semantic guidelines that you should try to follow when raising exceptions in your own programs.
The raise
built-in lets you raise specific exceptions:
def palindrome_detector(s):
if not isinstance(s, str):
raise TypeError("palindrome_detector expects type str")
s = s.lower().replace(" ", "")
return s == s[::-1]
The assert
built-in can be used to raise an AssertionError
. It is very commonly used in writing unit tests:
def test_palindrome_detector():
ex = 'Lisa Bonet ate no basil'
expected = True
result = palindrome_detector(ex)
assert result == expected, "For '{}', expected {}; got {}".format(ex, expected, result)
You can think of the final line as a short version of
if result != expected:
raise AssertionError("For '{}', expected {}; got {}".format(ex, expected, result))
There will be situations in which you expect your program to raise exceptions and you want to handle them rather than letting them crash the program.
For example, the following code seems to make the assumption that the input consists of a list of things that can be turned into an int, and it uses exception handling to tell the user about any instances in which that assumption proves false.
import warnings
def int_converter(vals):
new_vals = []
for x in vals:
try:
x = int(x)
new_vals.append(x)
except ValueError:
warnings.warn("Couldn't convert {}".format(x))
return new_vals
Note: I specified the exception that I expect to see (ValueError
). This is optional, but it is always good to do, so that your program does stop if something truly unexpected happens. If there are multiple errors that you want to handle, then you can give them as a tuple (e.g., (TypeError, ValueError)
.)
If you want something specific to happen where no exception is raised, you can optionally include an else
clause:
def int_converter_else(vals):
new_vals = []
for x in vals:
try:
x = int(x)
except ValueError:
warnings.warn("Couldn't convert {}".format(x))
else:
new_vals.append(x)
return new_vals
The above is probably better style than int_converter
, since one really want to do as little as possible inside the try
block so that it is easier to ensure that any exceptions raised really have the source that you expect.
Just to round this out, there is also an optional finally
clause, where you can specify code that will execute whether an exception was raised or not:
def int_converter_else_finally(vals):
new_vals = []
for x in vals:
try:
x = int(x)
except ValueError:
warnings.warn("Couldn't convert {}".format(x))
else:
new_vals.append(x)
finally:
print("Handled {}".format(x))
return new_vals
Here's an example of exception handling used as a strategy for adding new keys to a dictionary:
def counter_tryexcept(vals):
d = {}
for x in vals:
try:
d[x] += 1
except KeyError:
d[x] = 1
return d
This might be the fastest way to implement a simple count dictionary!