Exceptions

Exceptions

Errors that occur during runtime are called exceptions.

Exceptions in the Wild

What are some exceptions that we've seen? That is, what errors have occurred during runtime?

Exception Examples

Traceback (most recent call last):
  File "/tmp/exceptions.py", line 1, in <module>
    5 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
  File "/tmp/exceptions.py", line 1, in <module>
    int("foo")
ValueError: invalid literal for int() with base 10: 'foo'

Traceback (most recent call last):
  File "/tmp/exceptions.py", line 2, in <module>
    print(a[3])
IndexError: list index out of range

Traceback (most recent call last):
  File "/tmp/exceptions.py", line 1, in <module>
    "foo" + 5
TypeError: cannot concatenate 'str' and 'int' objects

A Closer Look at a Runtime Error

Traceback (most recent call last):
  File "/tmp/exceptions.py", line 1, in <module>
    "foo" + 5
TypeError: cannot concatenate 'str' and 'int' objects

We see the following details:

Types of Exceptions

A list of all exception types can be found at: http://docs.python.org/3.2/library/exceptions.html

The base exception is just Exception, but there are specific ones after that. From the previous slides, and our past experience programming, some exceptions we've seen include:

What do all of These Errors Mean!?

So Many Exceptions

The root Exception, and the other exception types that follow from it is called an exception hierarchy

Great, so Now What?

We can gracefully recover from exceptions!

For example, a common source of runtime errors is user input…

A Short Program

Let's write a simple interactive program that converts inches to feet:

inches = input("inches\n>")
print(float(inches)/12)

Soooo… That Works Great

Let's try running the program…

Everything works fine until? Is there a certain kind of input that will cause an error in this program?

inches
>asdf
Traceback (most recent call last):
  File "foo.py", line 2, in <module>
    print(float(inches)/23)
ValueError: invalid literal for int() with base 10: 'asdf'

Can We Prevent This Error from Happening?

…Maybe! Let's try a couple of things.

How about isdigit and isnumeric?

s = 'asdf'
print(s.isdigit())
print(s.isnumeric())

But they don't let legitimate input through!

s = '3.2'
print(s.isdigit())
print(s.isnumeric())

"Defensive Programming" Continued

Any other ways to allow strings like 3.2 in, but still prevent strings that are not composed numbers and a decimal point?

EAFP

Sometimes it's…

Easier to Ask Forgiveness than Permission

Exception Handling

There's a construct in most programming languages that lets you handle exceptions. In python, that construct is a try-except block. It's similar to an if-else:

try:
	# stuff that I want to do
except:
	# stuff to do if an error occurs

try-except

try-except Example 1

What is the output of this code?

a = [1, 2, 3]
try:
	print(a[100])
except:
	print("sorry, try another!")
sorry, try another!

try-except Example 2

What is the output of this code?

a = [1, 2, 3]
try:
	print(a[0])
except:
	print("sorry, try another!")
1

Let's Take Another Look at Our Conversion Program

Let's modify our program so that it behaves in a similar way, but uses try-except instead of testing with an if statement first.

inches = input("inches\n>")
print(float(inches)/12)
inches = input("inches\n>")
try:
    print(float(inches)/12)
except:
    print("don't do that")

Other Exceptions

We saw that we could handle a ValueError in our program. Can that exception happen in the following program? Are there any other exceptions (that we just talked about in an earlier slide) that can happen?

#  of slices in a pie
people = input("how many people are eating pizza?\n>")
print("Each person can have %s slices" % (8/int(people)))

Fixing the Pizza Pie Problems

How do we fix it (original code below)?

#  of slices in a pie
people = input("how many people are eating pizza?\n>")
print("Each person can have %s slices" % (8/int(people)))
#  of slizes in a pie
people = input("how many people are eating pizza?\n>")
try:
    print("Each person can have %s slices" % (8/int(people)))
except:
    print("No one's gettin' nuthin'")

Fixing the Pizza Pie Problems Continued

What if we want to deal with ValueErrors and ZeroDivisionError differently?

Say either:

  1. That's not a number! (ValueError)
  2. More for me! (ZeroDivisionError)

Specific Exceptions, Multiple Except Blocks

You can catch specific exception types, and add an arbitrary number of except blocks for every exception type that may occur.

try:
	# some tricky stuff
except NameOfErrorType1:
	# handle it gracefully
except NameOfErrorType2:
	# handle it gracefully too

Back to Pizza

So… let's apply that to our pizza program

people = input("how many people are eating pizza?\n>")
try:
    print("Each person can have %s slices" % (8/int(people)))
except:
    print("No one's gettin' nuthin'")
people = input("how many people are eating pizza?\n>")
try:
    print("Each person can have %s slices" % (8/int(people)))
except ZeroDivisionError:
    print("More for me!")
except ValueError:
    print("That's not a number!")

Implement Three Card Monte

Here's a text version of three card monte.

pick a cup: 0, 1, or 2
>0
['.', 'o', 'o']
you win

pick a cup: 0, 1, or 2
>0
['o', '.', 'o']
you lose

pick a cup: 0, 1, or 2
>asdf
['.', 'o', 'o']
not a number
you lose

pick a cup: 0, 1, or 2
>400
['o', '.', 'o']
that cup doesn't exist
you lose

Three Card Monte Requirements

A Potential Solution

import random
cups = ['o', '.', 'o']
random.shuffle(cups)
n = input("pick a cup: 0, 1, or 2\n>")
result = None

print(cups)
try:
    result = cups[int(n)]
except IndexError:
    print("that cup doesn't exist")
except ValueError:
    print("not a number")

if result == ".":
    print("you win")
else:
    print("you lose")