Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.Sign up
Using a GUI Python Debugger with Sage (PUDB)
The PUDB Python debugger provides a colorful ncurses-based "text-GUI" debugger that can be called by the local Sage python interpreter to debug Python scripts that interface with built-in Sage functions and types. In this tutorial, we provide a small sample script that interfaces with Sage and detail howto debug with PUDB in this case. The separate non-math-related tutorial for PUDB located at this link is a primary reference here, though we only reproduce the highlights of that full introduction to PUDB in this tutorial. We also cover installation of the PUDB module with the separate local Python that comes bundled with Sage in the next section, which is somewhat different than installing the module with a package manager such as
apt. Another video tutorial giving a concise overview of the PUDB debugger is found at this link.
Installation of PUDB in Sage
With a Working Local Source Install of Sage
The actual installation of the PUDB module in Python is straightforward once you know a couple of tricks about how the Sage shell can be used to install new packages in its own local chroot-like environment. In particular, assuming you have the OpenSSL development libraries installed on your system (try
apt-get install libssl-dev on a Debian variant Linux box), we can issue the following commands in sequence to get Sage's local Python to recognize the PUDB add-on:
$ sage -i openssl $ sage -f python2 $ sage -sh (sage-sh) pip install --upgrade pip (sage-sh) pip install pudb (sage-sh) exit
This method also works to install other Python add-on modules into Sage's Python, for example, such as the useful
memory_profiler module that is not yet installed in Sage by default. To verify that the install of PUDB is working correctly, consider any fairly simple Python script
test-script.py and then issue the following command:
$ sage -python -m pudb test-script.py
This should bring up the colorful vim-like ncurses interface to our new GUI debugger for Sage. To exit this screen, type q and navigate to the
< Quit > link to leave the application (more on these shortcuts in the next sections).
Another convenience when using the long sage
-python command is to add an alias to your local
~/.bashrc file using the next commands.
$ echo "alias sage-debug='sage -python -m pudb'" >> ~/.bashrc $ source ~/.bashrc
Then PUDB can be run on our sample test script from above by typing
sage-debug test-script.py from within your terminal. Additional information on customizing PUDB with themes, hacks, including other add-ons to PUDB itself (such as a custom "stringifier" for displaying variables) are detailed in the following blog posts:
CoCalc has PUDB pre-installed systemwide so it is possible to start up PUDB by typing the command
pudb myscript.py (we notice that we can also call
import pudb from within the Python source file itself). Note that the full sage command
sage -python -m pudb myscript.py suggested in the previous install notes does not work in the CoCalc terminal.
An Example Program
from sage.all import Integer, golden_ratio, var, fibonacci, binomial, sgn, n def AssertVType(obj, vtype, excpt = ValueError): if not isinstance(obj, vtype): raise excpt('Object should be of type ' + vtype) return False ## return True ## def Assert(cond, excpt, emsg = ""): if not cond: raise excpt(emsg) return False ## return True ## def IsInteger(iobj): return isinstance(iobj, int) or iobj.is_integer() ## class PhiPoly(object): def __init__(self, a = Integer(0), b = Integer(0)): self._a = a self._b = b ## @property def a(self): return self._a ## @a.setter def a(self, a): self._a = a ## @property def b(self): return self._b ## @b.setter def b(self, b): self._b = b ## def __str__(self): str_sign_func = lambda i, pos, neg, zero: \ pos if sgn(i) == 1 else neg if sgn(i) == -1 else zero rstr = "%s * phi %s %s" % (self.a, str_sign_func(self.b, "+", "-", ""), \ str_sign_func(self.b, self.b, abs(self.b), "")) return rstr ## def __float__(self): return n(self.a * golden_ratio + self.b) def __list__(self): return [self.a, self.b] ## def __tuple__(self): return (self.a, self.b) ## def __repr__(self): return str([self.a, self.b]) ## def __radd__(self, rhs): return self.__add__(rhs) ## def __add__(self, rhs): rop = PhiPoly(a = self.a, b = self.b) if IsInteger(rhs): rop.b += rhs else: rop.a += rhs.a rop.b += rhs.b return rop ## def __pow__(self, p): Assert(IsInteger(p), ValueError, "Exponent must be an integer") coeffs = [ [binomial(p, r) * (self.a ** r) * (self.b ** (p-r)), r] \ for r in range(0, p + 1)] return PhiPoly.fromcoeffs(coeffs) ## def __xor__(self, p): return self.__pow__(p) ## def is_integer(self): return self.a == 0 ## @classmethod def fromexpr(ppcls, expr, phi = golden_ratio): x = var('x') coeffs = expr.subs(phi == x).coefficients() return ppcls.fromcoeffs(coeffs) ## @classmethod def fromcoeffs(ppcls, coeffs): from_pow_func = lambda (coeff, pexp): \ ppcls(a = coeff * fibonacci(pexp), b = coeff * fibonacci(pexp - 1)) pow2phipolylst = map(from_pow_func, coeffs) return sum(pow2phipolylst) ## ## PhiPoly
from PhiPoly import * if __name__ == "__main__": mpowupper = 12 constant_term = 2 ppoly_pows_list =  for m in range(1, mpowupper + 1): phipoly_expr = (golden_ratio + constant_term) ** m phipoly = PhiPoly.fromexpr(phipoly_expr) ppoly_pows_list += [(m, phipoly)] print "[m = %2d]: (phi + %s) ** %2d == %s" % \ (m, constant_term, m, phipoly) ## print ppoly_pows_list ##
Description of the Program
PhiPoly class represents a polynomial in the
golden_ratio constant with integer coefficients.
Since we know the identity that
golden_ratio ** p = fibonacci(p) * golden_ratio + fibonacci(p-1) whenever
p >= 1 is integer-valued, we can always reduce the polynomial expansions of this type to an integer linear combination of the
golden_ratio. The main runner function
pudb-main-runner.py is loop-based (as opposed to using, say,
map to obtain the same result) to demonstrate the debugging features of PUDB when interfacing with code using built-in Sage types and functions. It computes a list of the first
12 mth powers of
golden_ratio + 2 and stores these resulting linear combinations of the
golden_ratio as tuples of the form
(m, (golden_ratio+2) ** m). Notice that at the top of the
PhiPoly.py file we are particularly selective about which built-in Sage types and functions we allow in our program (instead of, say, including them all with
from sage.all import *). The reason for this is that the variables list in the PUDB interface is far to long for the purposes of instruction when all of the built-in Sage machinery is imported at once.
To enter the PUDB debugger, we issue the following command at our terminal (or use the alias
sage-debug defined above):
$ sage -python -m pudb pudb-main-runner.py # Local Sage install $ pudb pudb-main-runner.py # CoCalc Sage install
Core Features of PUDB
Once inside PUDB, we can tweak several initial settings in the preferences menu with the shortcut
<CTRL+p>. The image below shows an example of setting the theme for PUDB and turning on line numbers in the display of our working code. The preferences dialog also appears at runtime the first time PUDB is started.
The execution of PUDB is easy enough to follow if you are familiar with another debugger application (otherwise see the user-friendly links cited in the first section of this tutorial). The following list provides a reference of keyboard shortcuts for some of the most useful and common operations available in PUDB:
- n: Execute next command
- s: Step into a function
- c: Continue execution
- b: Set a breakpoint on the current line
- e: Show the traceback from a thrown exception
- q: Opens dialog to either quit or to restart the running program
- o: Show the original console / standard output screen
- m: Open a module in a different file
- L: Go to line
- !: Go to the Python command line subwindow at the bottom of the screen
- ?: Display the help dialog which includes a complete listing of shortcut commands
- <SHIFT+V>: Switch context to the variables subwindow on the right of the screen
- <SHIFT+B>: Switch context to the breakpoints subwindow on the right of the screen
- <CTRL+X>: Toggle contexts between the lines of code and the Python command line
Once focused on a line of code in the PUDB display, pressing b will set a breakpoint at that line where execution stops after continuing with the c shortcut. By navigating to the Breakpoints subwindow by pressing <SHIFT+B>, we can press enter on each of the set breakpoints to configure additional properties about them. One particularly useful option is to setup a variable condition under which the breakpoint applies. The next image shows one example of configuring such a condition.
There are other ways to set breakpoints within the program. For example, from within the Python source file, we can call
from pudb import set_trace as bp; bp() to launch the PUDB debugger at a set point in the code. A list of previously stored breakpoints is stored in the configuration file
~/.config/pudb/saved-breakpoints-2.7 which stores breakpoint entries like the one set in the previous image in the form of
b /path/to/source/files/pudb-main-runner.py:15, (m % 4) == 0, though this format can run into problems if the source code is modified and the line numbers change in the file.
Printing Variables on the Python Command Line
The user can use the shortcut ! to shift into the built-in Python command line at the bottom of the screen. Once in the command line, any Python command may be run, including those that involve initialized variables in the local context of the program. The next terminal screenshot provides an example.
Changing Printed Variable Representations
One last matter of configuration in PUDB to cover is how to setup the way that individual variables are displayed in the right-hand-side subwindow. In particular, the default behavior is not nearly as informative in our case as having the Variables window display the corresponding string representations of the local objects. Once we press <SHIFT+V> and use the arrow keys to focus on an individual variable, pressing <ENTER> brings up the configuration dialog shown in the next screenshot (try viewing the
str() representations and expanding the view on a
The next screenshot image provides a view into the new displays for the
PhiPoly and Sage
Expression objects after we change the
ppoly_pows_list variables using the previous dialog. Note that in order for these variables to appear in the Variables subwindow, we need to have started the execution of the program into the loop by repeatedly pressing n, or by pressing c in conjunction with set breakpoints in the source.
Note that we can configure the Variables list to always display string representations, as well as set other subwindow sizing properties, using the example
~/.config/pudb/pudb.cfg file given below:
[pudb] breakpoints_weight = 0.5 current_stack_frame = top custom_stringifier = custom_theme = display = auto line_numbers = True prompt_on_quit = True seen_welcome = e027 shell = internal sidebar_width = 0.75 stack_weight = 0.5 stringifier = str theme = classic variables_weight = 1.5 wrap_variables = True