In this section we will look at exceptions:
- Program errors.
- What are exceptions?
- Exceptions types.
- Catching exceptions.
- Using else with exceptions.
- Using finally with exceptions.
- Throwing exceptions.
Computer programs sometimes go wrong. There are three broad types of error you will encounter when you are writing code:
- Syntax errors.
- Logical errors.
- Runtime errors.
Syntax errors occur when the code you type in isn't valid. This is often due to typing errors or misunderstanding Python syntax. For example:
s = [1, 2, 3 # Missing end bracket 1a = 3 # Variable name can't start with a number foor i in range(10): # Misspelling for do_something() do_something_else() # Wrong indentation
This code is full of mistakes, and Python simply won't run it until you fix things. These errors are usually the easiest to find and fix because Python highlights them.
Logical errors are where you have typed invalid Python code, but the code you typed in doesn't do what you thought it would. For example:
x = 10 if x <= 10: print('x is less than 10')
The problem here is that the code is valid but logically incorrect. The programmer wanted the message to only be displayed is
x was less than ten, but the code actually checks if
x is less than or equal to ten. These types of error can be more difficult to spot because they often only happen in specific circumstances (the code above works for any number except 10). You can only really eliminate these bugs by testing your code thoroughly. But the good news is, once you have spotted the bug it happens every time (the code will always go wrong for the value 10), which makes it easier to find.
Runtime errors are things that go wrong because of external factors. For example:
- If your program saves a file to disk, it will fail if the disk is full.
- If your program reads data from the internet, it will fail if the network is disconnected.
- If your program asks the user to type in a number, but they type in "hello" instead.
You can write extra code to check for these things, but you can't catch everything. For example, your program might check that the network is connected before it tries to read some data, but what happens if someone unplugs the cable while the data. This is where exceptions come in.
What are exceptions
Here is an example of exceptions in action:
age = int(input('How old are you?')) print('You are', age, 'years old')
This code works fine provided the user types in a number. But if they type in something else, such as "hello", the program terminates with a console message:
Traceback (most recent call last): File "test.py", line 1, in <module> age = int(input('How old are you?')) ValueError: invalid literal for int() with base 10: 'hello'
The problem here is that the
int function is being handed a value
int cannot convert this string into a number, it can't return a sensible value. So the
int function doesn't return in the normal way at all. Instead, it raises an exception. This exception causes Python to abandon the normal running of the program, and jump right out of the program back to the console.
This type of behaviour is called raising an exception because it only happens in exceptional circumstances (in this case, when the user provides bad input). It is also sometimes called throwing an exception, which means the same thing.
When an exception is thrown, Python also provides an
Exception object that gives more information about the causes of the error.
The console detects that the program has raised an exception, and displays the message above. The information in the error message comes from the
One final aspect of exceptions that is incredibly useful is that you can catch and exception in your own code. This allows your code to check the
Exception object, deal with the problem, and carry on running. Here is an example:
try: age = int(input('How old are you?')) print('You are', age, 'years old') except: print('Invalid age value') age = None
We will explain this in more detail below, but basically, we have placed our main code in a
try block, and our error handling code in an
except block (the syntax is similar to
else). The way this works is that if an exception is raised in the
try block, it is caught and causes the
except block to run, which in this case simply prints a message and sets the
age to None. The program then continues as before.
In no exceptions occur within the
try block, the
except block is completely skipped.
There are dozens of built-in exceptions, we will look at some of the common ones here.
This type of exception is thrown if your code tries to import something that doesn't exist:
from math import sqrt # This is ok, math has a sqrt function from math import xyz # ImportError, math has no xyz function
This type of exception is thrown if your code tries to access an out of range list element:
k = [1, 2, 3, 4] # k has 4 elements k # This is ok, element 1 exists k # IndexError, there is no element 6, the list is only 4 long
This type of exception is thrown if as operation fails because of the type of data:
len('abc') # This is ok, the length of the string is 3. len(10) # TypeError, you can't find the length of an integer
This type of exception is thrown if an operation fails because of an invalid value, for example:
a = int('1') # This is fine, '1' can be coverted to and integer. a = int('hello') # ValueError, 'hello' is not a number
This type of exception is thrown if you try to divide a number by zero, for example:
a = 1/0 # ZeroDivisionError
We have already seen how to catch an exception. We will now look at this is a bit more detail.
Here is a program where we ask the user for a number, and display the corresponding day of the week:
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] print('The day is', day)
Clearly we have the same problem as with our earlier age program - the user could enter an invalid string. We can use the same solution:
try: days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] except: print('Invalid day number') day = None if day: print('The day is', day)
Only catch exceptions you can handle
The code above will catch any exception that gets thrown when the code in the
try block runs.
Generally, it is better to only catch the exceptions that you are intending to handle. If a completely different exception occurs, that our software knows nothing about, it is usually best to let it go. Some other part of the system might be set up to handle that exception properly, it is best to allow that to happen.
We can do that by specifically catching a particular exception, for example we would expect a
ValueError if the user typed in an invalid string:
try: days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] except ValueError: print('Invalid day number') day = None if day: print('The day is', day)
So now our code will handle a
ValueError and carry on working.
But if some other type of exception occurs, for example, a network error, our code can't do anything about it. We ignore any unwanted exceptions and hopefully the code that called our code will handle it.
However, we do need to make sure we handle all the exceptions we can. In the code above, what if the user typed in
int function would covert the string into an integer, but
days[day_no] would throw an
IndexError because there are only 7 days in the
days list. We actually need to check for both types of error. We can do it like this:
try: days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] except (ValueError, IndexError): print('Invalid day number') day = None if day: print('The day is', day)
In this case, we are using a tuple of exception types
(ValueError, IndexError) and the
except clause applies to any type in that tuple. You need to put brackets around the tuple.
Alternatively, we can have different
except clauses for each type, like this:
try: days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] except ValueError: print('Invalid number string') day = None except IndexError: print('Day number must be 0 to 6') day = None if day: print('The day is', day)
This allows us to handle the two exceptions differently.
Accessing the exception message
Exceptions often contain additional information about what went wrong. You can access the exception within the
except block like this:
try: days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] except ValueError as e: print(e) day = None except IndexError as e: print(e) day = None if day: print('The day is', day)
as makes the exception object available to our code in the variable
This time, instead of printing a custom message, we print the exception object itself.
You will sometimes want to do both - you can display a helpful, custom message to explain what has gone wrong to the user, and also display the error content to help debug the problem.
Using else with exceptions
We can add an
else clause to the end of our
try statement, like this:
try: days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] except ValueError: print('Invalid number string') except IndexError: print('Day number must be 0 to 6') else: print('The day is', day)
else clause only gets called if no exception occurs. In this case, we are using it to print the result.
In all cases, the code executes exactly one of the clauses - either a one
except clause or the
Using finally with exceptions
If you add a finally block to a try statement, it will always get executed at the end, no matter what:
try: days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] day_no = int(input('Enter a day number 0-6 ')) day = days[day_no] except ValueError: print('Invalid number string') except IndexError: print('Day number must be 0 to 6') else: print('The day is', day) finally: print('All done!')
finally clause always runs:
- If an exception occurs and is caught, the matching
exceptclause runs, followed by the
- If no exception occurs, the
elseclause runs (if there is one), followed by the
- Even if an unexpected exception occurs, there will be no matching
exceptclause, but the
finallyclause still runs.
Although we have used
finally here to print a message, it is usually used for "tidying up" type task, such as closing any files that the program might have been using. This ensures that the program always gets a chance to do what it needs to do, even if an error occurs.
Consider this code:
def divide(a, b): return a/b print(divide(3, 2)) print(divide(3, 0))
In the first
divide(3, 2) returns 1.5, which is printed. In the second
divide(3, 0) throws a divide by zero error.
Our code could check the value of
b for zero, then raise a different exception that provides more specific information about he problem:
def divide(a, b): if b == 0: raise ValueError('b cannot be zero') return a/b print(divide(3, 2)) print(divide(3, 0))
This time if
b is zero, the code raises a
ValueError with a message saying that
b cannot be zero.
You can also catch and re-raise exceptions:
def divide(a, b): try: return a/b except ZeroDivisionError: raise ValueError('b cannot be zero') print(divide(3, 2)) print(divide(3, 0))
In this case, rather than checking for
b being zero, we just calculate
b is zero, this will throw a
ZeroDivisionError exception. We then catch this exception and throw a
ValueError exception. This allows us to swap one exception for another.
2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart polygon positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest