To use xdbg, you will first need to start an IPython session. This could be in the form of a Jupyter notebook (as presented here), but xdbg will work in most IPython sessions including the ipython
command-line console. Your text editor may also have a way of interacting with IPython (an example would be Atom with the hydrogen package).
To begin, you need to to ensure that xdbg
is loaded into your current session.
%load_ext xdbg
When loaded, IPython is extended with extra debugger-related magics:
%break
[func [lineno]]%tbreak
[func [lineno]]%enable
[bpnumber ...]%disable
[bpnumber ...]%ignore
bpnumber [count]%scope
name%makescope
nameThe %break
magic lets you set breakpoints inside a function, and execute interactive statements at that point.
For example:
def foo(n):
pass
%break foo
val = foo(5)
The message above indicates that the scope of the interactive iterpreter has changed.
All commands now target the scope inside the function. For example, the variable n
is now defined.
n
Any variables defined now are also defined inside the local scope. For example:
x = 5
'x' in globals()
Issuing a return
statement exits the function scope
return 5
val
x
As you see, execution continued (val
is set), and the local variable x
is no longer in scope.
xdbg
also provides some typical debugger functionality, such as setting breakpoints mid-function and enabling/disabling them.
def foo(n):
up_to_n = list(range(n))
print(up_to_n)
%break foo ?
%break foo 3
%disable 1
foo(5)
%enable 1
foo(5)
up_to_n
return
Note that xdbg
does not provide commands for stepping through the execution of a function or walking up/down the stack. Instead it drops you straight into an interactive REPL at the location of the breakpoint.
However, the IPython interactive environment is much more powerful than a typical debugger, because it lets you execute a multitude of Python commands. You can simulate stepping through the execution of a function by pasting th source code of that function into the REPL. You can also inspect the stack using the inspect
module. Commands for stepping through execution are of course very convenient, so they may be added to future versions of xdbg
.
In addition to debugging functions, xdbg
also lets you move the interpreter scope inside any module.
We are currently in the main scope, and the function xdbg.bar
is not defined
__name__
import xdbg
xdbg.bar
Now we can switch into the xdbg
module and define foo
.
%scope xdbg
__name__
x = 5
def bar():
print('x is', x)
# With no arguments, scope returns to the main module
%scope
__name__
xdbg.bar()
Note how in the definition of bar
we used the variable x, which is not defined in the main module. This is the main advantage of using %scope
: you can write code exactly the way it would appear in the module's file. This allows you to copy-paste code between your debugging session and the module without changing it (or even avoid copy-pasting entirely by using a Jupyter-enabled editor such as Atom with the hydrogen package).
Sometimes you may need to enter another file's scope before the module is fully imported -- for example, if the toplevel code in the file contains a bug. Consider the following situation:
%%file util.py
raise NotImplementedError("Code here is not ready yet!")
import util
The util module here cannot be imported, so there is no scope to jump to
%scope util
In this situation, you can use %makescope
, which makes the scope available while not populating it with any code.
%makescope util
Note how this leads to the creation of a dummy module that stands in for util
:
import util
util.__file__
It is then possible to move the interpreter into that module to populate its contents:
%scope util
x = 1
%scope
util.x