Exceptions: try, except, and raise
A trapeze artist still slips sometimes. The net underneath doesn't stop the fall. It stops the fall from being the end of the show.
Throughout this track, programs have crashed with messages like ValueError and ZeroDivisionError. In Debugging Python Programs, you learned to read them. But some of these problems aren't bugs in your code at all:
- A user types
seventeenwhen you asked for their age. - A file your program needs has been moved or deleted.
- The internet connection drops halfway through a download.
You can't prevent these. What you can do is plan for them, so your program responds sensibly, with a clear message or a second chance, instead of crashing. In Python, these problems are called exceptions, and you handle them with try and except.
A safety net
A trapeze artist practices for years, but still, occasionally, misses a catch. That's why there's a net. The net doesn't stop the fall. It catches it, so the artist can climb back up and carry on.
When something goes wrong in Python, the line that failed raises an exception: an object describing the problem. If there's a safety net, an except block, waiting to catch it, the program carries on. If there's no net, the exception falls all the way out of your program, and it crashes with a traceback.
try and except
Put the risky code in a try block, and the safety net in an except block right after it:
typed = "seventeen"
try:
age = int(typed)
print("Next year you'll be", age + 1)
except ValueError:
print(f"'{typed}' isn't a number. Please use digits, like 17.")
print("The program keeps running.")'seventeen' isn't a number. Please use digits, like 17.
The program keeps running.Here's what happens:
- Python runs the
tryblock, line by line. int(typed)fails and raises aValueError.- Python immediately stops running the
tryblock. The line that would print "Next year..." is skipped. - Python jumps to the matching
exceptblock and runs it. - After the
except, the program carries on normally.
If nothing goes wrong in the try block, the except block is skipped entirely.
Asking again until the answer is valid
Put try and except inside a while True loop from break and continue, and you can keep asking until the user gets it right. This is the fix promised back in Reading User Input:
while True:
try:
age = int(input("How old are you? "))
break
except ValueError:
print("Please type a whole number, like 17.")
print("Thanks! You're", age)How old are you? seventeen
Please type a whole number, like 17.
How old are you? 17
Thanks! You're 17break only runs if int() succeeded, so the loop only ends with a good answer.
Several kinds of problems
Different problems might need different responses. You can have several except blocks, and Python uses the first one that matches:
scores = [88, 94, 72]
typed_index = "5"
try:
index = int(typed_index)
print("Score:", scores[index])
except ValueError:
print("That's not a number.")
except IndexError:
print(f"There's no score number {typed_index}.")There's no score number 5.If two kinds need the same response, catch them together, in parentheses: except (ValueError, IndexError):.
Getting the details: as
Add as and a name to get the exception object itself. Printing it shows Python's own message:
try:
result = 10 / 0
except ZeroDivisionError as error:
print("Something went wrong:", error)Something went wrong: division by zeroDon't catch everything
It's tempting to write a net that catches every possible problem:
try:
age = int(input("Age: "))
except:
print("Something went wrong.")Don't. A bare except: catches everything, including bugs you'd want to know about, like a misspelled variable name, and even your attempts to stop the program with Ctrl + C. Your program keeps going with bad data, and you get no clue why.
Catch the specific exceptions you expect and have a plan for. Let real bugs crash loudly, so you notice and fix them. And never write an except block that just does nothing: if you can't handle the problem usefully, at least print a message.
else and finally
A try statement has two more optional parts:
elseruns only if thetryblock finished without an exception. It's a good place for code that should only run when everything worked.finallyruns no matter what: after success, after an exception, even if there's areturn. It's the place for cleanup that must always happen.
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Can't divide by zero.")
else:
print("Result:", result)
finally:
print("Done dividing.")
divide(10, 4)
divide(10, 0)Result: 2.5
Done dividing.
Can't divide by zero.
Done dividing.You'll see finally-style cleanup again in Reading and Writing Files, where Python's with statement does it for you.
Raising your own exceptions
So far, Python's own code has done the raising. Your code can raise exceptions too, with raise. This is the right response when a function is given data it can't work with:
def set_grade(level):
if not 1 <= level <= 12:
raise ValueError(f"Grade level must be 1 to 12, got {level}")
return level
print(set_grade(11))
print(set_grade(15)) # error: ValueError: Grade level must be 1 to 12, got 15raise ValueError("...") creates an exception with your message and throws it. The function stops right there, and whoever called it can catch it with except ValueError, or let the program crash with a clear message.
Raising is better than quietly "fixing" bad data, like silently changing 15 to 12. It makes mistakes visible right where they happen, instead of letting a wrong value cause a confusing problem much later.
ValueError fits most "this value doesn't make sense" situations, and TypeError fits "this is the wrong kind of value." For bigger programs, you can even make your own exception types, with a class that inherits from Exception, using the inheritance you learned in Inheritance:
class OutOfStockError(Exception):
pass
try:
raise OutOfStockError("No more adobo today.")
except OutOfStockError as error:
print("Sorry:", error)Sorry: No more adobo today.Try it
This program loads a list of scores typed as text, where some entries are broken. It skips bad entries, raises its own exception for scores outside 0 to 100, and reports on every entry with finally. Predict every line.
Running Python here
Running Python right in the browser is coming to Syntaxia soon. For now, you can read and edit the code here. If you have Python installed on your computer (see Setting Up), save it as main.py and run it with python main.py (or python3 main.py on macOS and Linux).
Check your prediction
OK: 88
(checked '88')
Skipped 'ninety': invalid literal for int() with base 10: 'ninety'
(checked 'ninety')
Skipped '105': score 105 is outside 0 to 100
(checked '105')
OK: 72
(checked '72')
Skipped '': invalid literal for int() with base 10: ''
(checked '')
Average of 2 valid scores: 80.0"ninety" and the empty string fail inside int(), which raises a ValueError with Python's own message. "105" converts fine, but parse_score raises its own ValueError with a custom message. One except catches both kinds, since they're the same type. else only runs for the good entries, and finally runs for all five.
Try it yourself
- Add
"-3"and"100"to the entries. Predict what each one prints. - Change
parse_scoreso an empty string raisesValueError("empty entry")before callingint(). How does the output change? - Write a function
safe_divide(a, b)that returnsa / b, orNoneifbis zero, usingtryandexcept.
Check your understanding
Up next
Every program so far forgets everything the moment it ends. In Reading and Writing Files, your programs will save data to files and load it back next time, handling missing files with the try and except you just learned.
