Playing with Snakes
>
<

Chapter 7: Handling Errors

A good programmer looks both ways before crossing a one-way street.
-Anon

A Velociraptor can travel 25m/s in a dead run. Let's make a program which predicts how many seconds you'd last if one started chasing you, but you had a 20 meter lead.

  1. user_answer = float(raw_input("How fast (in m/s) can you run? "))
  2. if user_answer >= 25:
  3.     print "You'd survive! Though you're a liar!"
  4. else:
  5.     print "You'd last", 20/(25-user_answer), "seconds"
  6.  

Awesome! I'd last less than a second. Hrm. That's frustrating. I know! I'll be a real jerk, and run your program again, and this time I'll put in a letter as my speed!

How fast (in m/s) can you run? J
Traceback (most recent call last):
  File "escapeRaptors.py", line 1, in <module>
    user_answer = float(raw_input("How fast (in m/s) can you run? "))
ValueError: invalid literal for float(): J

Ha! I've crashed your program. Clearly, with my superior intellect and program crashing abilities, I'll never be caught by the velociraptor!

Wouldn't it be nice if you could stop me, and force me to acknowledge that I run as fast as, well, um, something slow.

picture

The problem here, as described by the error, lies on line 1. I'm trying to make the letter J a float. Of course, J is a letter (String, really), not a float. So Python flips out and crashes.

Enter try statements.

With a try statement, Python will try to do something. If it fails, Python will look for an except statement that describes the error afterward. If it finds one, it will handle the error gracefully and move on to the very next line of the program. So, a new plan:

  1. try:
  2.     user_answer = float(raw_input("How fast (in m/s) can you run? "))
  3. except ValueError:
  4.     print "Ha! Not so fast, smart guy!"
  5. if user_answer >= 25:
  6.     print "You'd survive! Though you're a liar!"
  7. else:
  8.     print "You'd last", 20/(25-user_answer), "seconds"
  9.  

Great! Ok, let's run it.

How fast (in m/s) can you run? J
Ha! Not so fast, smart guy!
Traceback (most recent call last):
  File "tmp.py", line 5, in <module>
    if user_answer >= 25:
NameError: name 'user_answer' is not defined

Wait! What‽ I thought we declared user_answer on line 2!

We did. But that line never happened.

picture

We can't simply catch the error. We've got to catch the error and force the user to go back and choose a new number. And since we don't know how many times we're going to need to force the user to do something, we use a while loop.

  1. while True:
  2.     try:
  3.         user_answer = float(raw_input("How fast (in m/s) can you run? "))
  4.         break          
  5.     except ValueError:
  6.         print "Ha! Not so fast, smart guy!"
  7. if user_answer >= 25:
  8.     print "You'd survive! Though you're a liar!"
  9. else:
  10.     print "You'd last", 20/(25-user_answer), "seconds"
  11.  

There's a couple of things going on here, which you haven't seen before, but which make sense if you think about how they read in English. Look at line 1: while True: That's easy! True is a value. It's always True. It's like saying while 1 == 1: That's forever!

So now think about our program. If the user gives us the input J, we'll throw an error, print an error message and ask them all over again. Perfect! But what about when they give us a number input on line 3?

Now, instead of throwing an error line 3 will actually happen. So we move on to line 4, where we hit the break statement. break literally breaks the while loop, and it's done. Not making sense? What we wrote above does the exact same thing as this program, but in fewer lines, and in a way that's easier to read.

  1. bad_answer = True
  2. while bad_answer == True:
  3.     try:
  4.         user_answer = float(raw_input("How fast (in m/s) can you run? "))
  5.         bad_answer = False
  6.     except ValueError:
  7.         print "Ha! Not so fast, smart guy!"
  8. if user_answer >= 25:
  9.     print "You'd survive! Though you're a liar!"
  10. else:
  11.     print "You'd last", 20/(25-user_answer), "seconds"
  12.  

The program that we wrote won't stop the user from quitting out of our program while it's running. At any time the user can press ctrl-c and be done. Let's say that we don't want them to be able to do that. Suddenly, we need to be able to catch multiple errors.

  1. while True:
  2.     try:
  3.         user_answer = float(raw_input("How fast (in m/s) can you run? "))
  4.         break          
  5.     except (ValueError, KeyboardInterrupt):
  6.         print "Ha! Not so fast, smart guy!"
  7. if user_answer >= 25:
  8.     print "You'd survive! Though you're a liar!"
  9. else:
  10.     print "You'd last", 20/(25-user_answer), "seconds"
  11.  

You can catch multiple errors by putting them in parenthesis, to make what's called a tuple. On the other hand, if you wanted to display different error messages, you could:

  1. while True:
  2.     try:
  3.         user_answer = float(raw_input("How fast (in m/s) can you run? "))
  4.         break          
  5.     except ValueError:
  6.         print "Ha! Not so fast, smart guy!"
  7.     except KeyboardInterrupt:
  8.         print "Ha! No control-c for you!"
  9. if user_answer >= 25:
  10.     print "You'd survive! Though you're a liar!"
  11. else:
  12.     print "You'd last", 20/(25-user_answer), "seconds"
  13.  

If you wanted to catch absolutely any error that could possibly happen, just don't specify the error:

  1. while True:
  2.     try:
  3.         user_answer = float(raw_input("How fast (in m/s) can you run? "))
  4.         break          
  5.     except:
  6.         print "Ha! Not so fast, smart guy!"
  7. if user_answer >= 25:
  8.     print "You'd survive! Though you're a liar!"
  9. else:
  10.     print "You'd last", 20/(25-user_answer), "seconds"
  11.  

Sometimes you don't want to print any error at all. You just want the program to handle the error and move on. You still need to have something in the except statement, otherwise Python will complain. Just use the pass statement, which literally does nothing at all - it's just a placeholder to make Python happy about doing nothing.

  1. while True:
  2.     try:
  3.         user_answer = float(raw_input("How fast (in m/s) can you run? "))
  4.         break          
  5.     except:
  6.         pass
  7. if user_answer >= 25:
  8.     print "You'd survive! Though you're a liar!"
  9. else:
  10.     print "You'd last", 20/(25-user_answer), "seconds"
  11.  

When you start using try and except, it gets to be really tempting to write this program:

  1. while True:
  2.     try:
  3.         user_answer = float(raw_input("How fast (in m/s) can you run? "))
  4.         if user_answer >= 25:
  5.             print "You'd survive! Though you're a liar!"
  6.         else:
  7.             print "You'd last", 20/(25-user_answer), "seconds"
  8.         break          
  9.     except:
  10.         print "Ha! Not so fast, smart guy!"
  11.  

This program will work just fine - it'll ask for input, catch all sorts of errors, and maybe even eventually print out an answer for us. But those try statements are being abused. Remember that computers are deterministic devices: you, as the programmer, are supposed to be in control of what's going on. try statements are for times when there's no way for us to be in control, like when the user is giving us input, or we're trying to open a file that may or may not exist.

But something worse can happen. If you have essentially your entire program in a try statement, there's a chance you'll mask an error. Imagine that we put a line of bad code in our program, say right above the break statement. Maybe a line that Python would normally throw an error on, like d = [5,6,7,8] * [4] (you can't multiply a list by a list). Our program would try to do that line, but throw an error. Because we've got a generic except statement, we'll catch all errors and carry on like nothing has gone wrong. Confusing at best, when the user keeps getting asked how fast they run, even though we've already told them how long they'd last. They might lose precious time they could be using to escape the evil Velociraptor.

You can find a complete list of errors on the Python Homepage.

Remember that if you can prevent an error from happening, that's almost always better than trying to catch the error with a try statement.

This website will be taken offline before the end of 2011