✢ Corrections
Learning Objectives
At the end of this sub-unit, students should
- know how to use information from IDLE to fix problems.
- know how to use debug printing to find source of errors.
IDLE Information
Starting from Python 3.11, error messages for syntax error becomes much better. Consider the following simple error due to lowercase and uppercase variables. Notice the error message from IDLE.
In the case of other kinds of errors, the error messages may not be as clear. But there are still error messages. We can use these error messages to try to narrow down the problem. Consider the following code.
Buggy.py | |
---|---|
To debug this, we need to act like a detective. The information are given, we need to look at it closely and see where it is coming from. The most relevant information is at the bottom. That is the immediate cause of the problem. Often, it is sufficient to only look at this. But let us try to debug from the top.
Buggy.py | |
---|---|
Explanation
Here we see that we are invoking p1(1, 2)
in the python shell (i.e., IDLE).
We know this from "<pyshell#2>"
that indicates this comes from shell.
This function call will eventually end up with an error.
Buggy.py | |
---|---|
Explanation
Here we see inside the function p1
, we are invoking p3(x, y)
.
The line number is given by IDLE.
This function call will eventually end up with an error.
Buggy.py | |
---|---|
Explanation
Here we see inside the function p3
, we are invoking p2(a) + p2(b)
at line 10.
This is the source of error because this is the last lines before the actual error message.
Buggy.py | |
---|---|
Explanation
The very last line shows the actual cause of the error.
IDLE is telling us the call p2(a)
or p2(b)
(or both) is missing an argument.
The missing argument is w
which is the second argument.
So by following the error messages slowly, we can narrow down the source of the problem. After we narrow down the source, the next step is to make corrections based on the problem requirements.
Only on Execution
Note that the error message as shown above will only be printed if there is an actual error encountered when running the program. Consider the following code.
Invert.py | |
---|---|
Additional Information
In some cases, information given by IDLE may not be sufficient to identify error caused by logic. In such cases, we want additional information. These information can be as simple as knowing which lines are actually executed to a more complete information of the state of the program at a particular line. The choices are up to you.
We will show one possible way to perform debugging that can be easily toggled.
This is called debug printing, shortened to dprint
.
The idea is to have a function dprint
that will print only if a particular flag is set to True
.
Debug Print
How can we use this to get additional information? Consider the following incorrect factorial function.
Factorial
If you already know the cause, kudos1 to you. But if you are having problem determining the cause, we can add debug printing and see what is happening in more details.
That is quite long.
We need to be patient and slowly trace through the debug message.
Here we can see that at Line 4, we have << res 0
.
But we started from res = 1
, how can it get reduced to zero?
So we look at the operation that modifies res
and we see res = res * val
.
Now we know that it depends on the value of the variable val
.
So we look at nearby information about val
.
Notice that at Line 3, we have >> val 0
.
Ah, this is not correct because anything times 0 will give 0.
So we should not use 0.
The correction is that we start val
from 1.
Factorial
Correct by Design
The best advice we can give to avoid such error is to do designing before actually writing any code. This will be explained more clearly in the subsequent units.
Still, once the number of functions increase, we need to remember all the potential peculiarities of each function. We would recommend writing "documentation string" on the function that states the assumption clearly. Consider the following factorial function again. This function is correct assuming that the input is always non-negative.
Is this a good code? If the problem description assumes that all values will be non-negative, then it solves the problem. But if we are using it with negative number, we will get an infinite loop. So there is an argument to make the function as general as possible.
Unfortunately, that may not be possible in all cases. So a compromise is to annotate our function to specify our assumptions very very clearly. We recommend using "documentation string" as follows.
What is the advantage of writing it that way? We may be writing this function a week before and by now, we forgot about the assumption that it only works for non-negative input. By writing the assumption, we can refresh our memories. Additionally, this information will be displayed by IDLE!
-
Shinichi Kudos to you because you are a great detective! ↩