Exceptions A: Why Error Handling Is Needed, try and except
- Explain what an Exception is and what happens to the program when it is not handled
- Write a
try/exceptblock that catches an error and handles it - Handle several kinds of Exceptions separately, and get the error’s details with
as - Understand why an “empty”
exceptthat catches everything is a dangerous habit
What will we learn?
- What an Exception is.
- Why real software has to handle errors and not crash.
- The
try/exceptsyntax. - Different handling for different kinds of errors.
Why do we need this?
Imagine a small calculator server: one client sends "hello" instead of a number, and the whole server crashes. All the clients who come after them get “Connection refused”.
In the real world:
- A user types “abc” in an age field. The app should not close.
- A file the program tries to read was deleted. It should report it, not crash.
- The internet went down in the middle of a request. It is worth trying again, or showing a message.
Errors will happen: bad input, missing files, an unstable network. The only question is what the software does when they happen.
Explanation from the basics
What is an Exception?
An Exception is an object that Python “throws” (Raise) when a Runtime Error happens. All the errors we saw yesterday (ValueError, ZeroDivisionError, TypeError…) are Exceptions.
When an Exception is thrown:
- Python stops the current line immediately.
- It looks for someone who will “catch” (Catch) the error.
- If nobody catches it, the program crashes and a Traceback is printed.
try / except is the way to catch.
A tightrope walker (your code) may fall (an Exception). Without a net, the fall is the end of the show (a crash). try = “the show is above a net”. except = the net, which catches the fall, and then you decide what to do: try again, tell the audience, or move on to the next act.
How it works
try:— “try to execute this block”.except SomeError:— “if aSomeErroris thrown inside thetry, jump here and execute this block instead of crashing”.- If no error was thrown, the
exceptblock is skipped. - After the
try/except, the program continues as usual.
Syntax
try:
# code that might fail
except ErrorType:
# what to do if ErrorType happened
# several error types, each handled differently
try:
...
except ValueError:
...
except ZeroDivisionError:
...
# get the error object itself (its message)
except ValueError as error:
print(error)
# same handling for several types
except (ValueError, TypeError):
...First code example
week-2/safe_age.py:
text = input("Enter your age: ")
try:
age = int(text)
print("Next year you will be", age + 1)
except ValueError:
print("That is not a whole number:", text)
print("Program continues normally.")Run 1 (valid input):
Enter your age: 30
Next year you will be 31
Program continues normally.Run 2 (invalid input):
Enter your age: abc
That is not a whole number: abc
Program continues normally.Breaking down the code line by line
| Line | Run 1 (30) | Run 2 (abc) |
|---|---|---|
text = input(...) | text = "30" | text = "abc" |
try: | Enters the block | Enters the block |
age = int(text) | Succeeds: age = 30 | A ValueError is thrown, and Python jumps straight to except |
print("Next year...") | Printed | Skipped. We never reach it |
except ValueError: | Skipped (no error) | Caught! |
print("That is not...") | Does not run | Printed |
print("Program continues...") | Printed | Printed, and the program did not crash |
The important point: the moment an error is thrown, the rest of the lines in the try do not run. That is why you put inside the try only what depends on the success.
Another example
A robust division calculator, with different handling for each kind of error and a loop that asks again. week-2/safe_divide.py:
def ask_number(prompt):
while True:
text = input(prompt)
try:
return float(text)
except ValueError:
print("Please type a number, for example 12 or 3.5")
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
print("Cannot divide by zero - returning 0")
return 0
x = ask_number("First number: ")
y = ask_number("Second number: ")
print("Result:", divide(x, y))Example run:
First number: ten
Please type a number, for example 12 or 3.5
First number: 10
Second number: 0
Cannot divide by zero - returning 0
Result: 0Breakdown:
return float(text)inside atry— if the conversion succeeds, the function returns immediately and also leaves the loop. If it fails,exceptprints a message, and the loop asks again. That way you “force” the user into valid input.- Each function catches the error that is relevant to it:
ask_numbercatchesValueError(input),dividecatchesZeroDivisionError(calculation). - An open question: should
dividereturn 0 on division by zero? Mathematically, that is a wrong answer! In many cases it is better not to catch it here, and let the caller decide. We will talk about it in the lesson onraise.
as error — getting the error’s details
try:
number = int("12.5")
except ValueError as error:
print("Conversion failed:", error)Conversion failed: invalid literal for int() with base 10: '12.5'error is the error object. Printing it shows the message that would have appeared on the last line of the Traceback.
Common mistake
Mistake 1: an empty except that catches everything.
try:
age = int(input("Age: "))
print("Double:", age * 2)
reslt = age / 0
except:
print("Something went wrong")There are three different problems here (input, division by zero, and even a missing letter in a variable name), and all of them are hidden under “Something went wrong”. It is impossible to debug. And worst of all: an empty except: also catches KeyboardInterrupt, so even Ctrl + C will not stop a program stuck in a loop like this!
The rule: catch only specific errors that you know how to handle. All the others continue upwards and crash, and that is good, because that is how you discover bugs.
Mistake 2: a huge try.
try:
# 40 lines of code...
except ValueError:
print("Invalid value")Which of the 40 lines failed? Unknown. Put in the try only the lines that may fail with the error you catch, as few as possible.
Mistake 3: “swallowing” an error silently.
try:
price = float(text)
except ValueError:
passpass means “do nothing”. The error disappears, and price may not exist at all, which will cause a confusing NameError a few lines later. Always do something: a message, an explained default value, or writing to a log.
Practice
The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.
Exercise 1: Safe conversion to a number
Write a function to_number(text) that returns the text as a whole number, and if the text is not a number — returns None, without the program crashing.
print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))Expected output:
42
None
7Show solution
def to_number(text):
try:
return int(text)
except ValueError:
return None
print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))42
None
7int("hello") throws a ValueError. Without try, the program would crash. With except ValueError we decide what happens instead.
Exercise 2: Different handling for each kind of error
Write a function safe_divide(a_text, b_text) that receives two texts, converts them to numbers and divides. It returns:
- the result of the division, if everything is valid;
"numbers only"if one of the texts is not a number;"cannot divide by zero"if the divisor is 0.
print(safe_divide("10", "4"))
print(safe_divide("10", "0"))
print(safe_divide("ten", "2"))Expected output:
2.5
cannot divide by zero
numbers onlyShow solution
def safe_divide(a_text, b_text):
try:
return int(a_text) / int(b_text)
except ValueError:
return "numbers only"
except ZeroDivisionError:
return "cannot divide by zero"
print(safe_divide("10", "4"))
print(safe_divide("10", "0"))
print(safe_divide("ten", "2"))2.5
cannot divide by zero
numbers onlyEach kind of error has its own except, with suitable handling. Python checks the except blocks in order and enters the first one that matches.
Exercise 3: Showing the error message
The following code fails with a ValueError. Wrap it in a try, catch the error with except ValueError as error, and print the text Could not convert: followed by the message Python saved in error:
number = int("12abc")Expected output:
Could not convert: invalid literal for int() with base 10: '12abc'Show solution
try:
number = int("12abc")
except ValueError as error:
print("Could not convert:", error)Could not convert: invalid literal for int() with base 10: '12abc'as error saves the error object in a variable. When you print it, you get the message Python created.
Exercise 4: Choosing from a list
You have a list of names. Write a function pick(names, position_text) that receives a position as text (starting from 1) and returns the name at that position. It returns "not a number" for input that is not a number, and "no such position" for a position that does not exist.
names = ["Dana", "Avi", "Noa"]
print(pick(names, "2"))
print(pick(names, "abc"))
print(pick(names, "9"))Expected output:
Avi
not a number
no such positionShow solution
def pick(names, position_text):
try:
position = int(position_text)
return names[position - 1]
except ValueError:
return "not a number"
except IndexError:
return "no such position"
names = ["Dana", "Avi", "Noa"]
print(pick(names, "2"))
print(pick(names, "abc"))
print(pick(names, "9"))Avi
not a number
no such positionTwo different errors in two places: int(...) may throw a ValueError, and accessing the list may throw an IndexError.
Exercise 5: An except that catches everything
def read_age(text):
try:
return int(text)
except:
return 0- Why is
except:without an error type dangerous? - Give an example of a real bug this code would hide.
- Fix the code.
Show solution
- An empty
except:catches every error — including ones we did not think of — and hides them. The code continues as if everything is fine, with a wrong value. - If someone passes
Noneby mistake instead of text, aTypeErroris thrown — a real bug in the code that called the function. Instead of seeing it, we silently get age 0. (EvenCtrl + Cis caught by an emptyexcept:.) - Catch only the error we expect:
def read_age(text):
try:
return int(text)
except ValueError:
return 0
print(read_age("30"))
print(read_age("thirty"))30
0Summary
- Exception = an error that Python throws at runtime. If nobody catches it, the program crashes.
try= “try”.except SomeError= “if this error happened, do this instead of crashing”.- The moment an error is thrown, the rest of the
tryis skipped. except SomeError as errorgives access to the error message.- Catch specific errors only, with a small
try. No emptyexcept:and nopassthat swallows errors.