Winpdb—A Platform Independent Python Debugger
-
Upload
denis-snow -
Category
Documents
-
view
227 -
download
2
Transcript of Winpdb—A Platform Independent Python Debugger
Winpdb—A Platform Independent Python Debugger
http://winpdb.org/
Installation for Windows First install the wxPython runtime (unicode version preferred)
http://www.wxpython.org/download.php#binaries
Download (click on) wxPython2.8-win32-unicode-py27 and execute
wxPython is interesting in its own right
It’s a wrapper for the cross-platform GUI toolkit wxWidgets (written in C++) for Python
It’s 1 alternative to Tkinter, which is bundled with Python
There is (on the same page) also an installer containing the wxPython demo, other samples, and wxWidgets documentation:
wxPython2.8-win32-docs-demos (an .exe file)
After installing the wxPython runtime, go to Winpdb download page
http://winpdb.org/download/
Under the heading “Winpdb for Windows”, click the “Winpdb source package” to get to
http://code.google.com/p/winpdb/downloads/detail?name=winpdb-1.4.8.zip&can=1&q=
Download the zipped Winpdb source package
Extract it’s content into a new folder (say, under C:\Python27)
Enter the new folder from a CMD terminal window and type
python setup.py install
Getting Started: simple.py See the tutorial at
http://code.google.com/p/winpdb/wiki/DebuggingTutorial
Start with a simple script, simple.py
"""A simple script.""“
a = 1b = 2
c = a + b
print c
In a CMD window, go to the folder with simple.py and type winpdb simple.py
The Winpdb window opens and begins “attaching” to the script (the “debuggee”)
There’s also a blank output terminal open in a separate window
The debugger launched the script
Halted execution before the 1st line was executed
Awaits your command
The Winpdb Window The Winpdb window has several parts
Source Browser
Appears in the top right of the window The “current line” (the next line
to be executed) is highlighted
The “C” next to the line number “1” is the status indicator
Console
Just below the source browser
Lets us interact with the debugger directly
Menu and Controls
At the top of the window
Explore this as you like
Debuggee State These frames monitor the state of debuggee
Appear in the left side of the window
The Namespace frame keeps track of variables and their values as the debugger executes the code Tracks debuggee data among 3 levels
Locals: data within the current scope Globals: data within the global scope Exception: for examining exception data
The Threads frame gives info on threads of the debuggee (Advanced)
The Stack (“S”) window frame displays the call stack ( “s”) Top frame is most recently called rpdb2.py (debugger backend) always at
lowest levels
Control Basics Focus on left half of control panel—explore right half buttons on your own
The Go button lets the debuggee run to whichever comes first
a breakpoint,
an exception, or
the end of the program
With no breakpoints or exceptions encountered, the debuggee runs as if executed from the Python interpreter
Click the Go button
The debugger moves through each line and down through the call stack and finishes running the script
The source browser shows the current line as rpdb2.setbreak()—a lot of code
The Stack frame wnidow shows only 1 frame in the stack, frame 0—relates to rpdb2.py
The terminal window opened by Winpdb contains “3”—output from simple.py
To restart the debugging session, click Restart under the File menu
The Next button executes the current line and proceeds directly to the next statement in the current scope
The status indicator character is currently at C—the debugger just entered the code at the current scope
Not actually ready to execute the current line
Click Next: The status character change from C to L
L indicates the debugger is prepared to execute the current line
Click Next again: The debugger moves to the next line, a = 1
Click Next again: The debugger executes this statement
The debugger now tracks the variable a and its value in the Namespace frame, at the bottom of the Locals tab
It shows type int and value 1
Click Next again: Execute b = 2
The Namespace frame now contains b as well as a
Click the Globals tab: both a and b are there too
Click Next again: c now appears in the Namespace frame
print c is the current line
Click Next: The status character changes from L to R
Indicates the current scope is complete and is prepared to return
Click Next again to allow the return
The debugger has started moving back through the stack
rpdb2.py is the file of the current stack frame
The calling function is StartServer
The source browser has updated to show the code of the new current line, now in the rpdb2.py file
The status character is R again: this scope is also prepared to return
Click Next until you reach the line with rpdb2.setbreak()
Same line the debugger ended on when we clicked the Go button
Breakpoint Basics A breakpoint is an instruction that tells the debugger to pause
execution once it reaches that line
Set a breakpoint for the line with the statement c = a + b
c is about to be defined
Restart the session
In the source browser window,
position your mouse between the line number and the statement c = a + b
Left-click
The line turns red, indicating a breakpoint
Can now run the code straight to this point, without having to click Next repeatedly
Click the Go button
The code executes and halts at the line with the breakpoint
Can inspect the values of a and b
Click Next to execute the statement and see the value of c
Click Go to finish execution of the debuggee
To unset a breakpoint, click on the line the same way we did to set the breakpoint
The highlighting disappears
Can set as many breakpoints as we like
Console Basics The console has all of the commands available from the control
panel and the menus—and many more
To restart the debugging session, click inside the console’s command area
Type in restart and Enter
Can execute the current line of code by typing in next (or just n)
Enter n to enter in the current scope,
then n again to execute the 1st statement,
and again to execute the 2nd statement
Now at the statement c = a + b
Console command exec (or x) takes a single statement as an argument
Executes that statement in the context of the current scope.
Currently, a is 1 (see the Namespace frame)
To change a’s value to 3, enter in the Command box
exec a = 3
The current statement is c = a + b
Click Next to execute it
Unlike before, the value of c is now 5
exec lets us create new variables in the current scope—e.g.,
x d = 4
Check the Namespace frame
Console command eval (or v) takes a single expression as its argument
Returns (in the trace produced in the console) the results of that expression in the debuggee’s current scope
Try the following
eval a + b
v b + d
Unlike exec, eval doesn’t affect the state of the debuggee
Console command jump (or j) takes a single argument, an integer representing the line number to jump to (forwards or backwards)
E.g., to reset a back to its original value, can jump back to the statement a = 1
Enter at the console
jump 3
The current line jumps back up to line 3
The status character indicates that the debugger is prepared to run the statement
Click the Next button to run it and change a back to 1
Now reset the value of c, which depends on a, back to what it was
We haven’t affected the value of b
So we can jump over that line to the line with c = a + b
jump 6
Executing this line changes c to 3
Wrap Up Covered the following:
What each frame of the Winpdb window displays and allows us to control
How to run a whole script, how to run a single line
How to restart a debugging session
How to set a simple breakpoint
How to manipulate variables and evaluate expressions in the current scope
Diving Deepe: divisibles.py The debuggee is now divisibles.py
A more complex script, with a function and a loop
Should take a positive integer and print out all integers that divide that integer
"""
Takes a positive integer as an argument and prints all of its divisors.
example usage:
python divisbles.py 100
"""
import sys
Continued
def is_divisible(a, b):
"""Determines if integer a is divisible by integer b."""
remainder = a % b
# if there's no remainder, then a is divisible by b
if not remainder:
return True
else:
return False
Continued
def find_divisors(integer):
"""Find all divisors of an integer and return them as a list.""“
divisors = []
# we know that an integer divides itself
divisors.append(integer)
# we also know that the biggest divisor other than the integer itself
# must be at most half the value of the integer (think about it)
divisor = integer / 2
while divisor >= 0:
if is_divisible(integer, divisor):
divisors.append(divisor)
divisor =- 1
return divisors
Continued
if __name__ == '__main__':
# do some checking of the user's input
try:
if len(sys.argv) == 2:
# the following statement will raise a ValueError for
# non-integer arguments
test_integer = int(sys.argv[1])
# check to make sure the integer was positive
if test_integer <= 0:
raise ValueError("integer must be positive")
elif len(sys.argv) == 1:
# print the usage if there are no arguments and exit
print __doc__
sys.exit(0)
else:
# alert the user they did not provide the correct number of
# arguments
raise ValueError("too many arguments")
Continued
# catch the errors raised if sys.argv[1] is not a positive integer
except ValueError, e:
# alert the user to provide a valid argument
print "Error: please provide one positive integer as an argument."
sys.exit(2)
divisors = find_divisors(test_integer)
# print the results
print "The divisors of %d are:" % test_integer
for divisor in divisors:
print divisor
But, when we run it,
E:\Some Folder>divisibles.py 100
The divisors of 100 are:
100
50
So try the debugger
E:\Some Folder> winpdb divisibles.py 100
Click Next, to end of docstring (C to L)
Click Next, to import
Click Next, to definition of is_divisible
Click Next, hop over this definition (not executed until called) to definition of find_divisors
Click Next, hop over this definition to
if __name__ == '__main__':
Click Next, into the try statement
Click Next, into the statement
if len(sys.argv) == 2:
Run the statements in there
Confirm test_integer is set to 100 (Look in Namespace)
Click Next, exit the try statement
Now at statement
divisors = find_divisors(test_integer)
Click Next, call find_divisors
Inspect result from this call, stored in the divisors value
The Namespace frame might be getting a bit cluttered
The Filter button, 3rd from right, controls which variables will appear in the Namespace frame
By default, set to Medium
Clicking it toggles to Maximum, to Off, and back to Medium
Click Next to move to the statement after the call
In the Namespace frame, see that the value of variable divisors is a list
Expand it—see the values at the various indices This with Filter: Maximum
We’ve found a problem: this function returns a list with only 2 elements
But haven’t inspected the code in the function
Stepping in Executed beyond where the problem lies
So restart, either
through the File menu (item Restart) or
the Console (type restart)
To confirm that the command line argument (100) was passed in, execute past the import sys statement
The sys module is then loaded
Evaluate sys.argv[1] in the command line
Console should show b’100’
Put a breakpoint where the function is called
Now get to this line by either
clicking the Go button or
entering go (shortcut g) in the console
The Step into button, or alternatively, the step command (shortcut s), lets us step into code in a called scope (e.g., function or method)
Can then run through that scope’s lines of code
Both Next and Step into execute 1 statement
But only Step into goes into a new scope
Click Step into
Now at function definition header, labeled “C”—just entered a new scope
In the Namespace frame, the Locals and Globals are different Locals has just 1 entry: the
variable integer Globals has the old Locals
Click Next or Step into to complete entering the new scope and proceed to the 1st statement.
Click Next: new variable divisors, an empty list
Click Next: 50 in the list
Click Next 2x: new variable divisor with value 50
Execute into the loop until you get to the statement
if is_divisible(integer, divisor):
Clicking Step into gets us to the declaration of is_divisible
The status is C
The formal parameters, with values, are in Locals
Note the stack
Execute successive lines
On reaching the return, get an R (current scope about to return)
Execute through the return, and back to
divisors.append(divisor)
Next again, to
divisor =- 1
Here’s the error—should be: divisor -= 1
Fix the error in the editor (e.g., Notepad++)
To check that we’ve fixed the script, restart the debugging session
Winpdb will re-execute as it was run previously, but with the updated code
Even retains any previous breakpoints set and command-line arguments
Restart the debugging session
Confirm there’s still a breakpoint on the line calling find_divisors
Click Go
Click Step into to get into the find_divisors scope
Step into the while loop and into is_divisible via the statement
if is_divisible(integer, divisor):
Step through to make sure all is OK
It should return True (50 divides 100)
Return to the scope of find_divisors
Step until you reach the altered statement: divisor -= 1
Execute this statement
Check (in the Namespace frame) that it decremented correctly
Step through another iteration of the while loop, into the next call of is_divisible
100 isn’t divisible by 49
So go through the logic paths, making sure is_divisible returns False
Step back to the scope of find_divisors
Confirm that
49 isn’t appended
divisor is properly decremented
Step back into the next call of is_divisible
Should be called with 100 and 48
Return to Caller Now at the declaration of is_divisible with the status of C
Seen enough, but already entered the scope The Return button, or console
command return (or r), get us quickly out of a scope
Executes the scope’s code, goes directly to the return to the calling scope
Click Return
Click Next, return to the calling scope of find_divisors
Click Next to go through a few more rounds of the while loop without going through the is_divisible code
When satisfied that the code is working, let the debugger handle the rest of the execution
Clicking Go lets the debuggee proceed to completion and exit
But, rather than the code executing cleanly,
an exception was raised
Click Yes
Exceptional Analyses Winpdb goes into Exception Analysis mode, as evidenced in 2 ways:
the Analyze Exception button (right side of the control panel—‘e’ on a blue background) now appears activated, and
“Analyze mode was set to ON.” appears in the console
Exception Analysis mode takes us to the state when the exception was raised and left uncaught
In the Exception tab in the Namespace frame, we see a ZeroDivisionError
The code frame shows a mod operation
Knowing the exception type, go back to the state of divisibles.py when it raised the ZeroDivisionError
Use the Stack data to help trace down what caused the exception
Stack frames numbered and ordered top-down, most recent (and lowest number—0) at the top
Filenames also shown—so we can limit our attention
Click on a stack frame to see in the source browser the current statement (a call) in that frame
Clicking on the top frame reveals the offending statement in the code frame (the mod)
The E next to this statement shows this to be the point where the ZeroDivisionError was raised
The Locals tab in the Namespace frame shows that b’s value in this frame is 0
Recall: b is given assigned as an argument during the call of is_divisible
So this 0 originated at a less recent frame of the call stack
Look at frame 1, which is find_divisors
See from the Locals tab in Namespace that divisor was set to 0 when passed to is_divisible
Odd: already fixed the bug that “decremented” divisor past 0
See whether we can repeat the error
Breaking Under the Conditions Suspect the bug is hidden beneath a loop
Any breakpoint in that loop will stop execution upon every iteration
Need a breakpoint that only breaks execution when we want it to
A conditional breakpoint remains inactive (code continues past it) until some specified condition is reached
Then the breakpoint becomes active and halts execution at the line where it’s set
Specifying a condition involves specifying an expression
So we can set these breakpoints only from the console
The console command bp sets a breakpoint
To set a conditional breakpoint at the console, enter
bp <line number>, <expression>
Set a conditional breakpoint to halt the while loop when divisor reaches 1
Then manually move through the code Restart
Look for the line number of the while loop statement:
while divisor >= 0:
and set a conditional breakpoint for when divisor reaches 1
bp 33, divisor == 1
Previous breakpoint gets in the way—find and eliminate it
The console bl command lists the ids of breakpoints
The 1st breakpoint, on line 65, has an ID of 0
Clear this breakpoint using the bc command
Takes as an argument the ID of the breakpoint to be cleared
Clear the first breakpoint with
bc 0
Click Go
Now at the if statement immediately within the while loop divisor should be 1
Step through
is_divisible returns True as it should (100 is divisible by 1)
divisor is decremented by 1
Return to the top of the loop
Click Step into to run the evaluation of the while loop condition
The body of the loop is executed again But it should stop once divisor reaches 0
In the editor, change
while divisor >= 0
to
while divisor > 0
Restart the debugging session
Clear all breakpoints either
by going to the menu, and selecting in Breakpoints (the menu) Clear All or
by going to the Console and using the bc command
bc *
Click Go
On the output terminal we see
The divisors of 100 are:
100
50
25
20
10
5
4
2
1