Errors#

Congratulations, you have now learnt most of what could be considered fundamental Python!

We’re going to take some time now to talk about errors. When first approaching programming, you might imagine that most of your time will be spent writing new code. In reality, as you may already have realised, we often spend much more time trying to fix the code we have already written. This is why it is extremely useful to understand errors, as they usually do a pretty good job pointing us towards the problems and bugs that we have to remove.

The structure of error messages#

Errors in Python can sometimes throw a lot of information at you at once. They can also be full of jargon, which can make it quite a daunting task to try and figure out what on earth it all means.

Let’s start with a simple and familiar example:

a = 10
b = '25'

a + b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[1], line 4
      1 a = 10
      2 b = '25'
----> 4 a + b

TypeError: unsupported operand type(s) for +: 'int' and 'str'

It is often easier to read error messages from the bottom up. Here we have a TypeError, so we know that the problem has something to do with the type of the variables we are using. The error message itself says unsupported operand type(s) for +: 'int' and 'str'. This one is relatively easy to interpret: “you cannot add an integer to a string”.

Now let’s look at the rest of the error. Above the TypeError message itself, we have something called a traceback. The error message tells us what went wrong, the traceback tells us where it went wrong. Here you can see ----> 4 a + b, this is an arrow pointing us to the line that causes the error. In this case, it is line \(4\) that causes the problem, so this is where the arrow points us to, with the other lines displayed above this showing us the rest of the code leading up to the issue itself.

So to summarise, you can read this example error like so:

  • There is TypeError which in this case tells us that we are trying to add a str to and int.

  • The traceback tells us that the error occurs on line 4.

  • This line is a + b, so we know that one of a or b must be a str, whilst the other must be an int.

  • We look at the lines leading up to line 4 and find that we have written b = '25', so sure enough, b is a str.

Having correctly analysed the error, it is now very simple to see how we can fix the problem:

a = 10
b = 25

a + b
35

Too much jargon!#

The example we just went through was something that we had seen before, so deciphering the meaning of the TypeError was not overly difficult. Let’s look at a more puzzling example:

import math

a = 5
b = 6
c = 7

solution_1 = (-b + math.sqrt(b ** 2 - 4 * a * c)) / (2 * a)
solution_2 = (-b - math.sqrt(b ** 2 - 4 * a * c)) / (2 * a)

print(f'The solutions are {solution_1} and {solution_2}')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[6], line 7
      4 b = 6
      5 c = 7
----> 7 solution_1 = (-b + math.sqrt(b ** 2 - 4 * a * c)) / (2 * a)
      8 solution_2 = (-b - math.sqrt(b ** 2 - 4 * a * c)) / (2 * a)

ValueError: math domain error

Here we are trying to find the roots of the quadratic equation \(5x^{2} + 6x + 7\) with the good old quadratic formula:

\[\frac{-b \pm \sqrt{b^{2} - 4ac}}{2a}.\]

Let’s have a read of the error message. At the bottom we have the error message itself, it’s a ValueError and tells us more specifically that there is a math domain error. The traceback shows us that the error occurs on line 7 when we try to calculate solution_1. We haven’t even seen a ValueError before, let alone a math domain error, so how do we figure this out?

Well let’s start with the basics: what is a ValueError? If you come across a new type of error and you want to understand what it generally means, you can look at the documentation for Python’s built-in exceptions (errors are a subset of exceptions). A quick Ctrl + f for “ValueError” gives us:

ValueError

Okay, so now we know that a ValueError occurs when the type of an argument to a function is correct, but the value is somehow inappropriate. The only function we are calling in this example is math.sqrt, so it must be that b ** 2 - 4 * a * c is somehow an inappropiate value for taking the square root. Let’s see what that value actually is:

b ** 2 - 4 * a * c
-104

And now we see the problem: we’re trying to take the square root of a negative number. The math library is not designed to deal with complex numbers, so math.sqrt is not capable of evaluating \(\sqrt{-104}\).

Admittedly we are not very interested in complex numbers in this course, so the solution to this problem is largely irrelevant, but nonetheless here is how we could solve this problem:

import cmath

a = 5
b = 6
c = 7

solution_1 = (-b + cmath.sqrt(b ** 2 - 4 * a * c)) / (2 * a)
solution_2 = (-b - cmath.sqrt(b ** 2 - 4 * a * c)) / (2 * a)

print(f'The solutions are {solution_1} and {solution_2}')
The solutions are (-0.6+1.0198039027185568j) and (-0.6-1.0198039027185568j)

Here we simply replace the math library with the cmath library (complex math), which has a sqrt function that is perfectly happy with negative numbers.

To be crystal clear, the point of this example is not to introduce you to the cmath library - we don’t care about that. The point is that we were faced with an error that we couldn’t understand, but we found a way to figure it out. Obviously in these labs, you should always be able to ask a demonstrator for help rather than trying to understand everything on your own, but it is still extremely useful to practice error comprehension so that you get better at spotting the bugs in your own code.