Skip to content

Commit 6b81673

Browse files
committed
Adding script and README.
0 parents  commit 6b81673

2 files changed

Lines changed: 406 additions & 0 deletions

File tree

README.md

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
The goal of this exercise is to learn some basics of debugging Python code.
2+
3+
# Contents
4+
5+
- [Getting set up](#getting-set-up)
6+
- [Learning objective](#learning-objective)
7+
- [The goal](#the-goal)
8+
- [The script](#the-script)
9+
- [Best practice 1: Put code into functions](#best-practice-1-put-code-into-functions)
10+
- [Best practice 2: Write modules not scripts](#best-practice-2-write-modules-not-scripts)
11+
- [Best practice 3: Use docstrings to document your code](#best-practice-3-use-docstrings-to-document-your-code)
12+
- [Best practice 4: Add tests to your docstrings](#best-practice-4-add-tests-to-your-docstrings)
13+
- [Acknowledgments](#acknowledgments)
14+
- [License](#license)
15+
16+
17+
# Getting set up
18+
19+
At this point, you should have
20+
(1) an account on [Github](https://github.com/) and
21+
(2) been introduced to the very basics of [Git](https://git-scm.com/).
22+
23+
1. Login to your [Github](https://github.com/) account.
24+
25+
1. Fork [this repository](https://github.com/joaks1/python-debugging), by
26+
clicking the 'Fork' button on the upper right of the page.
27+
28+
After a few seconds, you should be looking at *your*
29+
copy of the repo in your own Github account.
30+
31+
1. Click the 'Clone or download' button, and copy the URL of the repo via the
32+
'copy to clipboard' button.
33+
34+
1. In your terminal, navigate to where you want to keep this repo (you can
35+
always move it later, so just your home directory is fine). Then type:
36+
37+
$ git clone the-url-you-just-copied
38+
39+
and hit enter to clone the repository. Make sure you are cloning **your**
40+
fork of this repo.
41+
42+
1. Next, `cd` into the directory:
43+
44+
$ cd the-name-of-directory-you-just-cloned
45+
46+
1. At this point, you should be in your own local copy of the repository.
47+
48+
1. As you work on the exercise below, be sure to frequently `add` and `commit`
49+
your work and `push` changes to the *remote* copy of the repo hosted on
50+
GitHub. Don't enter these commands now; this is just to jog your memory:
51+
52+
$ # Do some work
53+
$ git add file-you-worked-on.py
54+
$ git commit
55+
$ git push origin master
56+
57+
# Learning objective
58+
59+
Learn how to use the Python debugging module.
60+
61+
62+
# The Python debugger
63+
64+
Almost all programs have bugs.
65+
Following good coding practices can help you minimize bugs, and using `print`
66+
statements can go a long way in helping you find and fix them.
67+
However, some bugs are tricky to find, and scattering `print` statements all
68+
over your code is not an efficient solution.
69+
70+
Thankfully, Python has a powerful debugger build right in:
71+
The `pdb` module (which stands for Python debugger).
72+
For this exercise, we will learn how to use the `pdb` to fix bugs in the
73+
`area_of_rectangle.py` script included in this repo.
74+
75+
76+
## Running the script through the debugger
77+
78+
First, open the `area_of_rectangle.py` script and read it over to get a sense
79+
of what it should be doing.
80+
Then, try running it:
81+
82+
$ python3 area_of_rectangle.py
83+
area_of_rectangle.py: Expecting one or two command-line arguments:
84+
the height of a square or the height and width of a rectangle
85+
86+
We got a message saying that the script expects one or two arguments: the
87+
height of a square or the height and width of a rectangle.
88+
Let's try running it again with two arguments:
89+
90+
$ python3 area_of_rectangle.py 3 6
91+
Traceback (most recent call last):
92+
File "area_of_rectangle.py", line 49, in <module>
93+
area = area_of_rectangle(height, width)
94+
File "area_of_rectangle.py", line 34, in area_of_rectangle
95+
area = height * width
96+
TypeError: can't multiply sequence by non-int of type 'str'
97+
98+
Oop, we definitely have some bugs to fix.
99+
100+
Now, let's try running the script through the Python debugger:
101+
102+
$ python3 -m pdb area_of_rectangle.py 3 6
103+
104+
You will see output like:
105+
106+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(3)<module>()
107+
-> "A script for calculating the area of a rectangle."
108+
(Pdb)
109+
110+
You are now inside the script, inside the Python debugger.
111+
The first `pdb` you should now is `h` or `help`. Type `h` and hit enter:
112+
113+
(Pdb) h
114+
115+
Documented commands (type help <topic>):
116+
========================================
117+
EOF c d h list q rv undisplay
118+
a cl debug help ll quit s unt
119+
alias clear disable ignore longlist r source until
120+
args commands display interact n restart step up
121+
b condition down j next return tbreak w
122+
break cont enable jump p retval u whatis
123+
bt continue exit l pp run unalias where
124+
125+
Miscellaneous help topics:
126+
==========================
127+
exec pdb
128+
129+
This shows you all of the `pdb` commands. You can get help for any of them
130+
typing them after `h`. For example:
131+
132+
133+
(Pdb) h l
134+
l(ist) [first [,last] | .]
135+
136+
List source code for the current file. Without arguments,
137+
list 11 lines around the current line or continue the previous
138+
listing. With . as argument, list 11 lines around the current
139+
line. With one argument, list 11 lines starting at that line.
140+
With two arguments, list the given range; if the second
141+
argument is less than the first, it is a count.
142+
143+
The current line in the current frame is indicated by "->".
144+
If an exception is being debugged, the line where the
145+
exception was originally raised or propagated is indicated by
146+
">>", if it differs from the current line.
147+
148+
The `l(ist)` command is very helpful for seeing where you are in the script.
149+
Try it:
150+
151+
(Pdb) l
152+
1 #! /usr/bin/env python3
153+
2
154+
3 -> "A script for calculating the area of a rectangle."
155+
4
156+
5 import sys
157+
6
158+
7
159+
8 def area_of_rectangle(height, width = None):
160+
9 """
161+
10 Returns the area of a rectangle.
162+
11
163+
164+
The `->` shows us that we are at the first line of code in the file, which is
165+
the docstring for the file.
166+
167+
Two useful commands to continue execution through the script are `n(ext)` and `s(tep)`.
168+
Both will execute the next line of code. They differ in what they do when that
169+
line of code calls a function.
170+
`s(tep)` will "step into" the function, whereas `n(ext)` will simply call the
171+
function and advance to the next line of code in the current context.
172+
173+
Let's use `n(ext)` to work our way through some of the script. Type `n` 5 times
174+
and take note of how it is advancing through the script:
175+
176+
(Pdb) n
177+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(5)<module>()
178+
-> import sys
179+
(Pdb) n
180+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(8)<module>()
181+
-> def area_of_rectangle(height, width = None):
182+
(Pdb) n
183+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(37)<module>()
184+
-> if __name__ == '__main__':
185+
(Pdb) n
186+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(38)<module>()
187+
-> if (len(sys.argv) < 2) or (len(sys.argv) > 3):
188+
(Pdb) n
189+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(44)<module>()
190+
-> height = sys.argv[1]
191+
192+
We are now on the `height = sys.argv[1]` line. Use `l` to confirm this:
193+
194+
(Pdb) l
195+
39 message = (
196+
40 "{script_name}: Expecting one or two command-line arguments:\n"
197+
41 "\tthe height of a square or the height and width of a "
198+
42 "rectangle".format(script_name = sys.argv[0]))
199+
43 sys.exit(message)
200+
44 -> height = sys.argv[1]
201+
45 width = height
202+
46 if len(sys.argv) > 3:
203+
47 width = sys.argv[1]
204+
48
205+
49 area = area_of_rectangle(height, width)
206+
207+
Use `n` one more time to define the `height` variable:
208+
209+
(Pdb) n
210+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(45)<module>()
211+
-> width = height
212+
213+
Now, you can use the `p` command to print the value of `height`:
214+
215+
(Pdb) p height
216+
'3'
217+
218+
If you try to print the value of `width` you will get an error, because it's not defined yet:
219+
220+
(Pdb) p width
221+
*** NameError: name 'width' is not defined
222+
223+
If you use `l` you will see that you are currently on the line that defines `width`:
224+
225+
(Pdb) l
226+
40 "{script_name}: Expecting one or two command-line arguments:\n"
227+
41 "\tthe height of a square or the height and width of a "
228+
42 "rectangle".format(script_name = sys.argv[0]))
229+
43 sys.exit(message)
230+
44 height = sys.argv[1]
231+
45 -> width = height
232+
46 if len(sys.argv) > 3:
233+
47 width = sys.argv[1]
234+
48
235+
49 area = area_of_rectangle(height, width)
236+
50
237+
238+
Go ahead and run the next line with `n` and then try printing `width` again:
239+
240+
(Pdb) n
241+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(46)<module>()
242+
-> if len(sys.argv) > 3:
243+
(Pdb) p width
244+
'3'
245+
246+
You can also use Python code within the debugger. Let's do that to check the
247+
type of `height` and `width`:
248+
249+
(Pdb) type(height)
250+
<class 'str'>
251+
(Pdb) type(width)
252+
<class 'str'>
253+
254+
Note, you can also change variables dynamically within the debugger.
255+
For example:
256+
257+
(Pdb) height = int(height)
258+
(Pdb) p height
259+
3
260+
(Pdb) type(height)
261+
<class 'int'>
262+
263+
Whenever you want to exit the debugger, simply use the `q(uit)` command.
264+
265+
## Using breakpoints
266+
267+
Rather than run the entire script through the debugger, we can add a single
268+
line of code to drop us into the debugger at any place in our code.
269+
270+
Open the `area_of_rectangle.py` script with your text editor and
271+
change the code in the `area_of_rectangle` function from:
272+
273+
```python
274+
if width:
275+
width = height
276+
area = height * width
277+
return area
278+
```
279+
280+
To:
281+
282+
```python
283+
if width:
284+
width = height
285+
import pdb; pdb.set_trace()
286+
area = height * width
287+
return area
288+
```
289+
290+
We simply added `import pdb; pdb.set_trace()` just before we calculate the area
291+
of the rectangle.
292+
293+
Now, you can run the script as you normally would:
294+
295+
$ python3 area_of_rectangle.py 3 6
296+
297+
(Notice, we did not have to invoke the pdb module like above; that is done by
298+
`pdb.set_trace()`)
299+
Notice that you get dropped into the `pdb` debugger:
300+
301+
> /home/jamie/Dropbox/projects/python-debugging/area_of_rectangle.py(35)area_of_rectangle()
302+
-> area = height * width
303+
(Pdb)
304+
305+
Now we can use `l` to see where we are:
306+
307+
(Pdb) l
308+
30 14
309+
31 """
310+
32 if width:
311+
33 width = height
312+
34 import pdb; pdb.set_trace()
313+
35 -> area = height * width
314+
36 return area
315+
37
316+
38 if __name__ == '__main__':
317+
39 if (len(sys.argv) < 2) or (len(sys.argv) > 3):
318+
40 message = (
319+
320+
We are at the line immediately after our `pdb.set_trace()` call. Now, we can
321+
use the debugger to inspect variables and continue to execute the script, just
322+
like we learned above. For example, use `p` and `type` to inspect the variables
323+
`height` and `width`:
324+
325+
(Pdb) p height
326+
'3'
327+
(Pdb) p width
328+
'3'
329+
(Pdb) type(height)
330+
<class 'str'>
331+
(Pdb) type(width)
332+
<class 'str'>
333+
334+
335+
# The exercise
336+
337+
Now that you know the basics of the Python debugger,
338+
use it to debug the `area_of_rectangle.py` script.
339+
340+
341+
# Acknowledgments
342+
343+
## Support
344+
This work was made possible by funding provided to [Jamie
345+
Oaks](http://phyletica.org) from the National Science Foundation (DEB 1656004).
346+
347+
348+
# License
349+
350+
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/deed.en_US"><img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/deed.en_US">Creative Commons Attribution 4.0 International License</a>.

0 commit comments

Comments
 (0)