Files and errorsChapter 83 of 114
Try...Except
Handle the errors you expect, and let the rest surface.
The basic form
try:
value = int("not a number")
except ValueError:
print("that was not a number")Output
that was not a number
The try block runs. If it raises something the except names, that block runs instead and the program carries on.
Catch what you expect
Naming the exception is the whole point. A bare except: swallows everything, including your own typos and the user's Ctrl+C:
values = ["10", "x", "30"]
total = 0
for text in values:
try:
total += int(text)
except ValueError:
print("skipping", text)
print(total)Output
skipping x 40
Seeing the error
try:
int("abc")
except ValueError as problem:
print("failed:", problem)
print(type(problem).__name__)Output
failed: invalid literal for int() with base 10: 'abc' ValueError
Several exceptions
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return "cannot divide by zero"
except TypeError:
return "those are not numbers"
print(divide(10, 2))
print(divide(10, 0))
print(divide(10, "x"))Output
5.0 cannot divide by zero those are not numbers
One block can catch several with a tuple:
try:
int("abc")
except (ValueError, TypeError) as problem:
print("bad input:", type(problem).__name__)Output
bad input: ValueError
else and finally
else runs when nothing was raised. finally runs either way, and is for cleanup:
def parse(text):
try:
value = int(text)
except ValueError:
print("could not parse", text)
else:
print("parsed", value)
finally:
print("done with", text)
parse("42")
parse("x")Output
parsed 42 done with 42 could not parse x done with x
Keeping the try block to the line that can fail, and putting the rest in else, means you cannot accidentally catch an exception from code you were not testing.
finally always runs
Even when the function returns from inside the try:
def read():
try:
return "value"
finally:
print("cleanup happened")
print(read())Output
cleanup happened value
That is what with uses underneath to close your files.
Exceptions are classes
They sit in a hierarchy, and catching a parent catches its children:
print(issubclass(FileNotFoundError, OSError))
print(issubclass(ValueError, Exception))
try:
open("missing.txt")
except OSError as problem:
print("caught as OSError:", type(problem).__name__)Output
True True caught as OSError: FileNotFoundError
Order your except blocks from most specific to most general, or the general one catches everything first.
The ones you will meet
| Exception | Raised when |
|---|---|
ValueError | right type, wrong value |
TypeError | wrong type entirely |
KeyError | dictionary key missing |
IndexError | list position out of range |
FileNotFoundError | the file is not there |
ZeroDivisionError | divided by zero |
AttributeError | no such attribute |
NameError | name is not defined |
Do not catch what you cannot handle
If there is nothing sensible to do, let it propagate. A crash with a traceback is far more useful than silence:
def load(text):
return int(text)
try:
load("abc")
except ValueError:
print("the caller decided what to do about it")Output
the caller decided what to do about it
The function itself did not swallow the problem. Handling belongs where there is enough context to choose a response.
Test yourself
2 questionsWhat is wrong with a bare 'except:' block?
Show the answer
It hides your own bugs, such as a misspelled name — Catch the specific exception you expect, and let everything else surface.
When does the else clause of a try run?
Show the answer
When the try block raised nothing — Keeping the try to just the risky line and the rest in else stops you catching the wrong exception.
Raising Exceptions
Signal a problem yourself, and define your own exception types.