Errors are a part of every programmer’s life and knowing how to deal with them is a skill on its own.
The way Python deals with errors is called ‘Exception Handling’.
If some piece of code runs into an error, the Python interpreter will raise an exception.
Types of Exceptions
Let’s try to raise some exceptions on purpose and see the exceptions they produce.
TypeError
First, try to add a string and an integer
'I am a string' + 32
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must be str, not int
IndexError
Now, try to access an index that doesn’t exist in a list.
A common mistake is to forget that sequences are 0-indexed, meaning the first item has index 0, not 1.
In this example, the list car_brands
ends at index 2.
car_brands = ['ford', 'ferrari', 'bmw']
print(car_brands[3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
NameError
If we try to print a variable that doesn’t exist.
print(my_variable)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'my_variable' is not defined
ZeroDivisionError
Math doesn’t allow division by zero, trying to do so will raise an error, as expected.
32/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
This was just a sample of the kinds of exceptions you might see on your daily routine and what can cause each of them.
Exception Handling
Now we know how to cause errors that will crash our code and shows us some message saying something is wrong.
To handle these exceptions just make use of the try/except
statement.
try:
32/0
except:
print('Dividing by zero!')
Dividing by zero!
The example above shows the use of the try
statement.
Put the block of code that may cause an exception inside the try
scope, if everything runs alright, the except
block is not invoked, but if an exception is raised, the block of code inside the except
is executed.
This way the program doesn’t crash and if you have some code after the exception, it will keep running if you want to.
Specific Exception Handling
In the last example the except
block was generic, meaning it was catching anything.
The good practice it to specify the type of exception we are trying to catch, it helps a lot when debugging the code later.
If I know a block of code can throw an IndexError
, specify it in the except
:
try:
car_brands = ['ford', 'ferrari', 'bmw']
print(car_brands[3])
except IndexError:
print('There is no such index!')
There is no such index!
You can use a tuple to specify as many exceptions types as you want in a single except
:
try:
print('My code!')
except(IndexError, ZeroDivisionError, TypeError):
print('My Excepetion!')
else
It is possible to add an else
command in the end of the try/except
. It runs only if there are no exceptions.
my_variable = 'My variable'
try:
print(my_variable)
except NameError:
print('NameError caught!')
else:
print('No NameError')
My variable
No NameError
Raising Exceptions
The raise
command allows you to manually raise an exception.
This is particularly useful if you want to catch an exception, do something with it, usually log the error in some personalized way like redirecting it to a log aggregator, or end the execution of the code since the error should not allow the progress of the program.
try:
raise IndexError('This index is not allowed')
except:
print('Doing something with the exception!')
raise
Doing something with the exception!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: This index is not allowed
finally
The finally
block is executed independent of exceptions being raised or not.
They are usually there to allow the program to clean up resources like files, memory, network connections, etc.
try:
print(my_variable)
except NameError:
print('Except block')
finally:
print('Finally block')
Except block
Finally block