5 | Dealing With Errors and Bugs


You’ve worked your way through the first few Chapters, and you’ve written plenty of code already. You must have come across some errors and bugs along the way. Even the most proficient programmers will have errors and bugs in their code as they’re writing it.

Learning how to understand and deal with errors and bugs is an important step when learning to code. Avoiding them may not be possible, but being able to resolve them quickly and efficiently is.

Errors and Bugs

You can understand the different ways things can go wrong through the following two examples. First, consider this statement:

If it is rained, taken your umbrella.

This phrase breaks the English language rules. In the context of a computer program, when there’s an error, the computer program will stop executing the code and it will not go further. You’ll get an error message whenever you’ve broken Python’s rules.

If you’re reading a text in English or any other human language, you can still read on when you encounter an error. You may be able to understand the meaning from the context of what you’ve read before and after. The same is not true when your computer is reading Python or any other programming language. Whenever you’re presented with an error message, usually written in red font, you’ll have to fix the error before the program can go further.

Now read the following phrase:

If it is raining, take your sunglasses and leave your umbrella at home.

There’s nothing grammatically wrong with this phrase. However, there’s a flaw in the logic. The instructions conveyed by this phrase are unlikely to be the ones intended. When there’s this type of bug in a computer program, the program will not stop and give an error message as no Python rules have been broken. However, the outcome will not be the one you want.

One of the challenges with this type of bug is that you need to find them first before you can fix them. Sometimes it will be evident that the outcome is wrong. If you get soaked in the rain when you leave your umbrella at home, you’ll notice. But such errors in the outcome of a program are not always so obvious. Fixing them is the second problem you’ll need to worry about. Finding them comes first!

The process of debugging is central to coding. There are methods and techniques you can use to find, understand, and fix errors and bugs. The process of debugging is similar to detective work. A well-known quote about debugging goes a bit further:

Debugging is like being the detective in a crime movie where you are also the murderer.

Filipe Fortes

In this Chapter, you’ll learn more about the techniques and tools you’ll need to be a good detective.

Understanding Error Messages

Start by considering the following snippet from a program designed to help a market seller keep track of what he’s sold. There are some problems with this code:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys()
     income = 0
      qty = input(f"How many {item}s have you sold? ")
     income = income + qty * items[item]
print(f"\nThe income today was £{income:0.2f}")

The variable items stores a dictionary with the names and prices of three items that the market seller has for sale. The loop is designed to go through each item in the dictionary, ask the user to type in the quantity of each item sold, and then work out the total income for the day.

There are a couple of uses of the f-string that you’ve learned about in earlier Chapters. In the last line, you’ll have noticed that inside the curly brackets {} in the f-string, the variable name income is followed by some additional notation. This tells Python to display the value of income as a float to two decimal places.

When you run the code above, exactly as it is, you’ll get the following error message:

  File "<path>/<filename>.py", line 3
    for item in items.keys()
                            ^
SyntaxError: invalid syntax

When you get an error message, your detective work should always start with the following two clues:

  • The location of the error — the file and line number in the first line of the error message
  • The type and description of the error — the last line of the error message

Since you’re only working on one file, the file name may seem redundant. However, in more complex projects, you’ll have several files, and therefore the file name is helpful. The line number is not always accurate because of how Python compiles the program, but it will be close to the actual location of the error. On most IDEs, you can usually click on the file name in the error message, and this will take you to the line in your code where the error is.

The last line of the error message shows that the type of this error is a SyntaxError. The syntax is the grammar and punctuation of the language. The description of the error just says invalid syntax in this case. You’ll see later on that this description can contain more information for other errors.

The offending line is also displayed in the error message with an arrow (^) pointing to the possible location of the error. Oops, the colon is missing. Let’s fix this error and try rerunning the code:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys():
     income = 0
      qty = input(f"How many {item}s have you sold? ")
     income = income + qty * items[item]
print(f"\nThe income today was £{income:0.2f}")

The error message is now on a different line and of a different type:

  File "<path>/<filename>.py", line 5
    qty = input(f"How many {item}s have you sold? ")
IndentationError: unexpected indent

The error type is now an IndentationError, and the description tells you that there’s an extra indent that shouldn’t be there. Indents are an integral part of Python’s grammar. Line 5 has an extra space at the beginning compared with line 4. Python interprets this as an indent. However, extra indentation must always follow a line ending with a colon, such as lines including a for, while, or if statement. Let’s fix this:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys():
    income = 0
    qty = input(f"How many {item}s have you sold? ")
    income = income + qty * items[item]
print(f"\nThe income today was £{income:0.2f}")

When you run your code now, it will seem like it works as you get the prompt you’re expecting. I’ll talk more about the phrase “it works” later on, as these words are quite possibly the two most dangerous words in programming!

Let’s input a value for the quantity of coffee sold and move on:

How many Coffees have you sold? 10

But as soon as you press Return/Enter, you’ll get yet another error message:

Traceback (most recent call last):
  File "<path>/<filename>.py", line 6, in <module>
    income = income + qty * items[item]
TypeError: can't multiply sequence by non-int of type 'float'

You’ve encountered yet another error type now, a TypeError. You learned a lot about data types in previous Chapters. This error message shows you that the problem is related to having the wrong data types in the wrong places. The error description is informative as well. The message tells you that the program can't multiply sequence by non-int of type 'float'.

I hope you’ve got your own Sherlock Holmes cap to put on. Let’s look at the clues. The issue is with the multiplication, and therefore the part of the line you should start looking at is qty * items[item] as it’s the part that has the multiplication in it. The error description refers to a sequence and a non-int. You may have guessed that non-int refers to a value that’s not an integer. The value is a float in this case.

However, the value of qty should be the number 10, and the value of items[item] should be the price of coffee, as item is equal to "Coffee" in the first iteration of the for loop. The price of coffee is the float 2.2, which represents £2.20. In the next section, you’ll learn about one of the most powerful investigative tools in debugging: the humble print() function.

Looking at Snapshots of The Data

The expectation that the variables you’re multiplying are the integer 10 and the float 2.2 must be wrong. When you want to debug code, you’ll generally want to know two things about the data that you’re using in your code:

  • What is the value of the data
  • What is the type of the data

You’ll also need to know this information at the point where the error happens, not at the beginning or end of the program. One of the simplest ways to get a snapshot of your data at any point you wish in your program is to place a print() in the right place.

As the error occurs on line 6, you can print out the value of the two variables of interest on the lines just before this to check that they contain the data you expect them to have:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys():
    income = 0
    qty = input(f"How many {item}s have you sold? ")
    print(qty)
    print(items[item])
    income = income + qty * items[item]
print(f"\nThe income today was £{income:0.2f}")

You’ll still get the error message as before, but before the error, you’ll see a printout of the values of the two variables:

How many Coffees have you sold? 10
10
2.2

Traceback (most recent call last):
  File "<path>/<filename>.py", line 8, in <module>
    income = income + qty * items[item]
TypeError: can't multiply sequence by non-int of type 'float'

This clue doesn’t lead you anywhere. The printouts seem to show that the values you get are what you were expecting. The next thing to check is the data type of these variables. Instead of printing out the values, you can now print out the type of data:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys():
    income = 0
    qty = input(f"How many {item}s have you sold? ")
    print(type(qty))
    print(type(items[item]))
    income = income + qty * items[item]
print(f"\nThe income today was £{income:0.2f}")

This code will now show the data types before the error message:

How many Coffees have you sold? 10
<class 'str'>
<class 'float'>

Traceback (most recent call last):
  File "<path>/<filename>.py", line 8, in <module>
    income = income + qty * items[item]
TypeError: can't multiply sequence by non-int of type 'float'

The value of items[item] is indeed a float, as you expected and as shown by the error message. However, the value of qty is a str. In Chapter 4, you read that a string is a sequence, which explains the use of this term in the error message.

You can always check that you’ve understood the problem the error message is pointing to by trying to replicate it in the Console:

>>> "hello" * 2.5
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'float'

You get the same error message in this example. When you multiply a string by a float, Python doesn’t know what to do. It gives you an error. You get the same error if you try to multiply a list by a float, and indeed any data type which is a sequence will give the same error:

>>> [34, 45, 23, 12] * 2.5
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'float'

The error message complains about trying to multiply by a non-integer. That’s because sequences can be multiplied by integers:

>>> "hello" * 2
'hellohello'

>>> [34, 45, 23, 12] * 2
[34, 45, 23, 12, 34, 45, 23, 12]

Let’s get back to the script with the market seller’s code. Your investigation led you to conclude that there’s something wrong with the value of the variable qty. In this case, the variable qty is being defined on the line before. That’s where the error must be. At this point, you may remember that input() always returns a string. You’ve encountered this bug in the first project you worked on in Chapter 1.

You can now fix this problem by converting the string to an integer. You can also remove the print() statements as they’ve done their job and they’re no longer needed:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys():
    income = 0
    qty = input(f"How many {item}s have you sold? ")
    qty = int(qty)
    income = income + qty * items[item]
print(f"\nThe income today was £{income:0.2f}")

When you run the program now, you’ll see that you can go through the entire for loop:

How many Coffees have you sold? 10
How many Teas have you sold? 10
How many Chocolates have you sold? 10

The income today was £25.00

Hurray! Finally, it works!

Looking For Bugs

Warning signals should have been triggered when you read the last two words in the previous section. “It works” are arguably the two most dangerous words in programming. Your code ran without giving any error messages. You didn’t break any of Python’s rules. But get your calculator out of the drawer, put some fresh batteries in and check the answer.

The total for 10 coffees, 10 teas, and 10 chocolates should be £62.00. Although there are no error messages, this program still has a bug as the value it prints out is wrong. This is a more dangerous type of bug since you don’t get any error messages. It’s up to you as a programmer to find these problems by trying out your program under different scenarios and checking that the values you get are what you’re expecting.

Hopefully, you haven’t packed your Sherlock Holmes cap away yet. Let’s investigate further by trying out a few different scenarios:

How many Coffees have you sold? 10
How many Teas have you sold? 0
How many Chocolates have you sold? 0

The income today was £0.00

Now, try with only 10 teas sold:

How many Coffees have you sold? 0
How many Teas have you sold? 10
How many Chocolates have you sold? 0

The income today was £0.00

And finally, you can input 10 for chocolates but not for the other items:

How many Coffees have you sold? 0
How many Teas have you sold? 0
How many Chocolates have you sold? 10

The income today was £25.00

This testing gave you another clue. The data in the dictionary items shows that a bar of chocolate costs £2.50. The income seems to show the value of the chocolates sold, but not of the other items. You can run the code with other scenarios to confirm your suspicion.

You know what the problem is but this doesn’t mean that the problem is solved. The issue seems to be with the variable income this time. So you can use the same tool as before and start by printing out the value of income:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys():
    income = 0
    qty = input(f"How many {item}s have you sold? ")
    qty = int(qty)
    income = income + qty * items[item]
    print(income)
print(f"\nThe income today was £{income:0.2f}")

The output now looks as follows:

How many Coffees have you sold? 10
22.0
How many Teas have you sold? 10
15.0
How many Chocolates have you sold? 10
25.0

The income today was £25.00

The value of income is being worked out correctly by the code as the outputs from print() show. However, it seems as though the variable income is not remembering the previous values each time the for loop runs.

The debugging process is there to give you clues. It will not automatically solve the problem for you. In this case, you now have enough clues to know that the value of income is resetting every time you go through the loop. Inspecting the code further, you’ll see that the line income = 0 is the problematic line. Each time the loop starts, whatever value the program has stored in the box labelled income is discarded and the value 0 is now stored in the box.

Let’s remove this problematic line:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

for item in items.keys():

    qty = input(f"How many {item}s have you sold? ")
    qty = int(qty)
    income = income + qty * items[item]
    print(income)
print(f"\nThe income today was £{income:0.2f}")

But as soon as you type in the first value, you’re back to getting an error:

How many Coffees have you sold? 10
Traceback (most recent call last):
  File "<path>/<filename>.py", line 7, in <module>
    income = income + qty * items[item]
NameError: name 'income' is not defined

You now get a NameError. The word name has a specific meaning in Python. Every variable, function, and indeed, every object in a computer program has a name. These are the labels on the boxes (variables), or the names of the books (imported modules), or the labels on the doors that lead to adjacent rooms (functions). The “name not defined” error occurs when the program can’t find any reference of the name anywhere in the White Room.

Line 7 is redefining income, so it needs the variable to exist and have a value already. This is why the line you have just deleted was there. However, it was in the wrong place. You need to create the variable and set it to 0 before the for loop starts. You can now also remove the print() you used to debug the value of income:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

income = 0
for item in items.keys():
    qty = input(f"How many {item}s have you sold? ")
    qty = int(qty)
    income = income + qty * items[item]
print(f"\nThe income today was £{income:0.2f}")

Finally, you can safely claim that the code works:

How many Coffees have you sold? 10
How many Teas have you sold? 10
How many Chocolates have you sold? 10

The income today was £62.00

When dealing with errors and bugs in your code, you often need to track what’s happening to data as the program runs. You want to look inside the program while it’s running. Using well-placed print() lines is a powerful way of doing so. You need to get a snapshot of:

  • the value of the data
  • the type of the data

Sometimes, knowing the value is the only clue you need. At other times, the data type is the key clue. You need to learn to be a good detective and gain experience with reading and understanding error messages in all instances.

Before moving to the next section, here’s a neater way of writing the for loop in this code. The example above worked well to explore errors and debugging. However, you know a better way of looping through a dictionary. You can write the following:

items = {"Coffee": 2.2, "Tea": 1.5, "Chocolate": 2.5}

income = 0
for item, value in items.items():
    qty = input(f"How many {item}s have you sold? ")
    qty = int(qty)
    income = income + qty * value
print(f"\nThe income today was £{income:0.2f}")

It would also make the code more readable if you didn’t call the variable items to avoid the rather unfortunate items.items() in the for loop statement. The first items is the name of the dictionary, while the second items() is the method associated with dictionaries. This process of making changes to your code to tidy it up, such as renaming variables or functions, is very common and is called refactoring.

Using More Advanced Debuggers

The print() function is both simple and powerful as a debugging tool. My advice is to stick with using this tool for now.

When projects become longer and more complex, including code spread across several files, other tools may come in handy. These tools are called debuggers. If you’re using an IDE such as PyCharm, then you already have a powerful debugger built-in to your IDE.

This section will not try to give a comprehensive overview of debuggers. Instead, I will illustrate their basic concept using PyCharm’s debugger.

Let’s go back to one of the errors you encountered earlier when you eventually discovered that qty was a string when you wanted it to be an integer. You can insert a breakpoint anywhere you wish in your program. In PyCharm, you can click in the margin next to the line where you want to insert the breakpoint. The IDE shows the breakpoint as a red dot in the margin:

Using PyCharm's visual debugger to deal with errors and bugs

Instead of choosing Run… to run your code, you can select Debug… from the same menu. The program will run as usual until it reaches the breakpoint, where it will pause. Here’s what the IDE will show you when you try this:

Using PyCharm's visual debugger to deal with errors and bugs

The line highlighted in blue has not run yet. The debugging tool allows you to look inside the program while the program is running. While the program is on hold on this line, you can see all the variables within the program at this point of execution. The variables are shown in the bottom panel of the window. For each variable, you can see both its data type and its value. This shows you that qty is a string which is the clue you’ll need to fix this error.

There are several icons above the list of variables that will allow you to move through your code. If you want to explore how to use this debugger, you can try to use the Step into my code button, allowing you to move through your code one line at a time. You can try this on the final version of the code or indeed on one of the programs you worked on in previous Chapters.

As you step through your program one line at a time, you’ll see the variables in the bottom panel change as the program changes the values stored within them.

The primary purpose of these debugging tools is to find and fix errors and bugs. From a learning perspective, you can use a debugger to help you understand better what happens inside a computer program as it runs. If you place your breakpoint early on in the program and then step into your code one line at a time with the debugger, you’ll be running your program in slow motion, controlling the execution speed. This tool allows you to peer inside the Python program while it’s running.

Conclusion

You will make errors. You will write bugs in your programs. You can’t avoid this, but you can learn how to find and fix errors and bugs quickly and efficiently.

As with everything else in programming, there’s no shortcut and no substitute for practice. Learning to code is like learning a musical instrument. You can read books and have lessons, but it’s the practice that makes the difference between knowing the theory and being proficient in the subject.

Debugging gets easier and less time consuming the more you do it. Another of the famous or infamous jokes in the coding world is the following:

Only half of programming is coding. The other 90% is debugging.

anonymous

And since you ask, here’s another one:

Sometimes it pays to stay in bed on Monday rather than spending the rest of the week debugging Monday’s code.

Dan Solomon/Christopher Thompson

All jokes rely on exaggeration, so don’t worry! But debugging does play a very important role in coding.

In this Chapter, you’ve covered:

  • How things can go wrong with errors and bugs
  • How to read and understand error messages
  • How to use the value and data types of variables to find and fix problems
  • How to use print() for debugging
  • How to get started with more advanced debugging tools

You’re now equipped with all the fundamental tools in programming. You’ve learned about loops using for and while, and conditional statements using if. You know how to use functions and define your own ones. And you’ve learned about several key data types that offer different ways for storing data. And now you can fix errors and bugs too.

You can also read the blog post Debugging Python Code Is Like Detective Work for more insights on debugging.


The Next Steps — Climbing The Tree

The next Chapter is an interlude in which you’ll revisit the White Room analogy and extend it a bit further. After that, you’ll start going deeper into programming.

This is a good time to introduce you to another useful analogy. Learning to code is like climbing a tree. The main tree trunk supports the whole tree, and you must start by climbing this main trunk before you can start to explore the smaller branches. There are many diverse applications of computer programming, but you need the same fundamentals for all of them.

You’ve now climbed high enough on the main trunk so that you can start choosing some of the branches to explore. There are still interesting features to discover on the main trunk, but that doesn’t mean you can’t begin to climb some of the branches.

In this book, you’ll be exploring those branches that lead to quantitative programming. This term covers coding applications in fields such as science, finance, and other data-driven areas. Some of these tools are relevant and useful in other applications of coding too.

Along the way, you’ll also climb some branches that are relevant to most programming applications but that are not on the main tree trunk. After the White Room interlude chapter, you’ll learn more about functions, and you’ll also explore Object-Oriented Programming and classes. These topics are required knowledge for any application of coding you may be interested in.

There’s another aspect of the tree to explore besides the main tree trunk and the many branches. The roots are hidden out of sight but are an essential part of the tree. A lot is happening behind the scenes in a computer language. Once you’ve explored the main trunk and some of the key branches, you’ll start to become more curious about the roots, too, as these will help you understand coding better.

We won’t dive underground much in this book, but we’ll talk about some of the things that happen behind the scenes from time to time. Indeed, the White Room analogy gives you a glimpse of this.

Subscribe to

The Python Coding Stack

Regular articles for the intermediate Python programmer or a beginner who wants to “read ahead”