Lecture Notes 11: Debugging And Practice with Loops
A Primer on Debugging
A Bug is a logical error in your code.
For example, the following code attempts to determine if a number is inside, outside, or at a limit of an interval:
num = eval (input ("give me a number: "))
if 0 < num < 1 :
print ("inside")
elif num < 0 and num > 1:
print ("outside")
else:
print ("limit")
Try it out
here
Activity 1 [2 minutes]:
Can you see anything wrong with this?
Another example: the following code attempts to obtain the factorial of a number (this time using a for loop):
def main():
num = 5
fac = factorial(num)
print("The result for {:d} is {:d}".format(num, fac))
def factorial(x):
mult = 1
for i in range(1,x+1):
mult *= x
return mult
if __name__ == "__main__":
main()
Try it out
here
Activity 2 [2 minutes]:
Can you see anything wrong with this?
Debugging
To
de-bug is to remove these logical errors from your code.
Failure cases
The first important step is to
identify a case (or many cases) for which your code fails.
That usually gives you enough information, but if you still can't see the problem, you proceed with debugging.
There are two main approaches:
- Using debugging print statements to track your progress inside the code
- Executing the code in debug mode, where it runs the lines one by one and let's you examine the contents of the variables
- (similar to debugging is using a visualizer like the python tutor... which is good for small programs)
Debugging Example 1
First step: failure cases.
- if we input a positive number.... it works
- if we input a limit number.... it works
- if we input a negative number.... it says "limit"...Which is a failure case
Now decide which approach might be smarter.
Print statements? We would need to add prints to check which conditions fail (which is a lot of extra code)
See an example
here
You will notice that even with all these prints, the problem is NOT in the individual comparisons... we would have needed the correct print!
Try the alternative prints
here
Now, let's try using the Debugger: Go to Debugging Intro in Replit and copy-paste the "intervals.py" code into main.py.
We'll use the debugger and try to identify the error.
Debugging Example 2
First step: failure cases.
- if we use num = 1 .... it works
- if we use num > 1 .... it returns a number that is too large!...Which is a failure case
let's try using the Debugger: Go to Debugging Intro in Replit and copy-paste the "factorial.py" code into main.py.
We'll use the debugger and try to identify the error.
Break and Continue!
One can exit a loop or skip to the start of the next iteration
This is sometimes helpful for "breaking out of a loop" or "avoiding unnecessary work".
You can used two reserved words in a loop:
- continue: If you want a loop to skip a section of the body and to continue in the next loop iteration
- break: If you want a loop to stop and never go into the next loop iteration.
Examples:
Check it out
Weird printing of odd numbers
Check it out
Escaping an infinite loop
More on nested loops (with debugging)
Let's see some examples:
Example 1: a number pyramid
We wish to generate the following structure:
First attempt (no nesting)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | def main():
pyramid() # prints 5 levels of numbers
def pyramid():
for j in range(1):
print(j+1, end=" ")
print()
for j in range(2):
print(j+1, end=" ")
print()
for j in range(3):
print(j+1, end=" ")
print()
for j in range(4):
print(j+1, end=" ")
print()
for j in range(5):
print(j+1, end=" ")
if __name__ == "__main__":
main()
|
Look at the code
Here
Activity 3 [2 minutes]:
In the same way we asked before, do you notice any code that is repeated?
Attempt 2: Nesting!
Let's use out old friend:
a loop to take advantage of "automated repetition!"
Steps:
- First, what is every repetition dependent on (what changes in each repetition)?
- Is this something we can use a variable for?
- If so, can we write a loop that viaries this variable?
Let's try it:
Steps:
- First, surround a single block of code ( the one that was "repeated") in a for loop (you can try a while later)
- Replace the changing parameter for the loop variable (let's use i)
- Set the varying range according to the initial range of desired values (1, 2, 3, 4, 5)
You should end up with something like this:
1
2
3
4
5
6
7
8
9
10
11
12 | def main():
pyramid() # prints 5 levels of numbers
def pyramid():
for i in range(1,5):
for j in range(i):
print(j+1, end=" ")
print()
if __name__ == "__main__":
main()
|
You can see it in action
Here!
Attempt 3: Nesting using input parameters
For greater versatility, we can change the number of times we repeat the outer loop based on some input parameter!
1
2
3
4
5
6
7
8
9
10
11
12 | def main():
pyramid(7) # prints <argument> levels of numbers
def pyramid(lim):
for i in range(1,lim+1):
for j in range(i):
print(j+1, end=" ")
print()
if __name__ == "__main__":
main()
|
Check it out
Here!
Building grids of prints with nested loops
The follwing are two ways to print a grid composed of the word "Yak":
Method 1: the power of print
def main():
for i in range(3):
print("Yak" * 4)
if __name__ == "__main__":
main()
Try out the code
here
Activity 4 [2 minutes]:
How would you modify this code so that the output loks like this:
Yak Yak Yak
Yak Yak Yak
Yak Yak Yak
Yak Yak Yak
Method 2: Using a loop and simple prints
def main():
for i in range(4):
for j in range (3):
print("Yak", end= "")
print()
if __name__ == "__main__":
main()
Try out the code
here
Activity 5 [2 minutes]:
How would you modify this code so that the output loks like this:
Yak Yak Yak Yak Yak
Yak Yak Yak Yak Yak
Yak Yak Yak Yak Yak
Yak Yak Yak Yak Yak
Homework
[Due for everyone]
Assignment 03 is out (Due 02/18 at 5PM)
[Optional]