• No results found

Recover from errors

In document Real Python Part 1 (Page 73-76)

You’ve probably already written lots of scripts that generated errors in Python. Run-time errors (so-called because they happen once a program is already running) are called exceptions. Congratu-lations - you’ve made the code do something exceptional

There are different types of exceptions that occur when different rules are broken. For instance, try to get the value of “1 / 0” at the interactive window, and you’ll see a ZeroDivisionError exception.

Here’s another example of an exception, a ValueError, that occurs when we try (and fail) to turn a string into an integer:

1 >>> int("not a number")

2 Traceback (most recent call last):

3 File "<pyshell#1>", line 1, in <module>

4 int("not a number")

5 ValueError: invalid literal for int() with base 10: 'not a number'

6 >>>

The name of the exception is displayed on the last line, followed by a description of the specific prob-lem that occurred.

When we can predict that a certain type of exception might occur, we might not be able to prevent the error, but there are ways that we can recover from the problem more gracefully than having our program break and display lots of angry red text.

In order to stop our code from breaking because of a particular exception, we use a try/except pair, as in the following:

1 try:

2 number = int(raw_input("Enter an integer: "))

3 except ValueError:

4 print "That was not an integer."

The first thing that happens in a try/except pair is that everything inside of the “try” block is run normally. If no error occurs, the code skips over the “except” block and continues running normally.

Since we said “except ValueError”, however, if the program encounters a ValueError (when the user enters something that isn’t an integer), we jump down to the “except” block and run everything there.

This avoids Python’s automatic error display and doesn’t break the script since we have “caught” the ValueError.

If a different kind of exception had occurred, then the program still would have broken; we only handled one type of exception (a ValueError) with our “except” block.

A single “except” block can handle multiple types of exceptions by separating the exception names with commas and putting the list of names in parentheses:

1 from __future__ import division

2

3 def divide(num1, num2):

4 try:

5 print num1 / num2

6 except (TypeError, ZeroDivisionError):

7 print "encountered a problem"

This isn’t used very frequently since we usually want our code to react specifically to each type of exception differently. In this case, we created adivide() function that tries to divide two numbers.

If one or both of the numbers aren’t actually numbers, a TypeError exception will occur because we can’t use something that isn’t a number for division. And if we provide 0 as the second number, a ZeroDivisionError will occur since we can’t divide by zero. Both of these exceptions will be caught by our except block, which will just let the user know that we “encountered a problem” and continue on with anything else that might be left in our script outside of the try/except pair.

More “except” error handling blocks can be added after the first “except” to catch different types of exceptions, like so:

1 try:

2 number = int(raw_input("Enter an non-zero integer: "))

3 print "10 / {} = {}".format(number, 10.0/number)

4 except ValueError:

5 print "You did not enter an integer."

6 except ZeroDivisionError:

7 print "You cannot enter 0."

Here, we might have encountered two different types of errors. First, the user might not have input an integer; when we try to convert the string input usingint(), we raise an exception and jump to the “except ValueError:” block, displaying the problem. Likewise, we could have tried to divide 10 by the user-supplied number and, if the user gave us an input of 0, we would have ended up with a ZeroDivisionError exception; instead, we jump to the second “except” block and display this problem to the user.

A list of Python’s built-in exceptions can be found here. It’s usually easiest to figure out the name of an exception by purposefully causing the error to occur yourself, although you should then read the documentation on that particular type of exception to make sure that your code will actually handle all of the errors that you expect and (just as importantly) that your program will still break if it encounters a different, unexpected type of exception.

We can also use an “except” block by itself without naming specific exceptions to catch:

1 try:

2 # do lots of hazardous things that might break

3 except:

4 print"The user must have screwed something up."

However, this is dangerous to do and is usually not a good idea at all. It’s easy to hide a poorly written section of code behind a “try/except” and think that everything was working fine, only to discover later that you were silencing all sorts of unexpected problems that should never have occurred.

Review exercises:

1. Write a script that repeatedly asks the user to input an integer, displaying a message to “try again” by catching the ValueError that is raised if the user did not enter an integer; once the user enters an integer, the program should display the number back to the user and end without crashing

In document Real Python Part 1 (Page 73-76)