Emacs: Python Debugging, Send Code to an Arbitrary Buffer

OpenERP development is part of my current job, which means I debug a lot of
Python code. This blog offers a few tips on how to debug Python in Emacs. In
theory, these tips could also be applied to any other dynamic programming
languages.

1 Entering Python Interactive Interpreter from PDB

There are a few different ways to debug Python code using PDB (The Python
Debugger, actually I use IPDB, an enhanced version of PDB). What I usually do
is insert the following line into the position where I want a break point:

import ipdb; ipdb.set_trace()

Of course, as a frequently used operation, I have found an Elisp function to
help me do it:

1:  (defun python-add-breakpoint ()
2:    "Add a break point"
3:    (interactive)
4:    (newline-and-indent)
5:    (insert "import ipdb; ipdb.set_trace()")
6:    (highlight-lines-matching-regexp "^[ ]*import ipdb; ipdb.set_trace()"))
7:  
8:  (define-key python-mode-map (kbd "C-c C-b") 'python-add-breakpoint)

This function will insert the Python code mentioned above and highlight it.

Then I create a shell in Emacs and run a Python program in the newly created
shell, when the program reaches the “break point” it will automatically get
into PDB.

Say if I have a Python source code file ~/test.py:

 1:  #!/usr/bin/env python
 2:  # -*- encoding: utf-8 -*-
 3:  #
 4:  # Author: 任文山 (Ren Wenshan)
 5:  
 6:  
 7:  def f(a, b):
 8:      return "%d - %d" % (a, b)
 9:  
10:  if __name__ == "__main__":
11:      for i in xrange(9, 1, -1):
12:          if i == 6: import ipdb; ipdb.set_trace()
13:          print f(i, i - 2)

You can see that I have set a break point at line #12. Now press M-x shell
to open a shell, and then run the Python file in it:

cd ~ && python test.py

It gets into PDB when the variable i is equal to 6:

 1:  wenshan@debian-vm-home:~$ cd ~ && python test.py
 2:  9 - 7
 3:  8 - 6
 4:  7 - 5
 5:  > /home/wenshan/test.py(13)<module>()
 6:       11     for i in xrange(9, 1, -1):
 7:       12         if i == 6: import ipdb; ipdb.set_trace()
 8:  ---> 13         print f(i, i - 2)
 9:  
10:  ipdb>

Now, if you want to redefine the f() method, you could do it in PDB. But that
isn’t convenient at all, alternatively, we could enter the Python interactive
interpreter, which is a much nicer programming environment, by executing the
following line in PDB:

!import code; code.interact(local=vars())

Again, I have written an Elisp function to do it because I use this often:

1:  (defun python-interactive ()
2:    "Enter the interactive Python environment"
3:    (interactive)
4:    (progn
5:      (insert "!import code; code.interact(local=vars())")
6:      (move-end-of-line 1)
7:      (comint-send-input)))
8:  
9:  (global-set-key (kbd "C-c i") 'python-interactive)

With this function, pressing C-c i (in PDB) will invoke an interactive
interpreter.

And now you could write code in the interpreter, interactively.

But I’m not satisfied with this, because Python is very strict with
indentation, so quite often you will have to rewrite your code for a few
times due to some small mistake. Hence, I want to write Python code in a
normal python-mode buffer, which brings auto indentation, error checking,
auto completion and etc, and send the code to the interactive interpreter for
execution.

2 isend: Installation and Configuration

The python.el that comes with the standard Emacs has code sending
functionality, but you are not allowed to specify the destination
buffer. Although it is said that the python-mode maintained by the Python
community could send code to an arbitrary buffer, it requires too much effort
for me to switch to it because my current configuration is based on the
standard python.el.

After Googling, I found isend-mode.el, which is a generic (could also be used
for Shell, Perl, Ruby, etc) code sending package for Emacs.

Clone isend-mode from Github to your local machine:

git clone git://github.com/ffevotte/isend-mode.el.git

Open the isend-mode.el file in the newly cloned directory and find the
following two lines:

1:  (defvar isend--command-buffer)
2:  (make-variable-buffer-local 'isend--command-buffer)

Delete the second line to make `isend–command-buffer’ a global variable,
this is for defining the advice (see next section).

Finally, add the following code to your Emacs configuration file:

1:  ;; Python send code
2:  (add-to-list 'load-path "~/.emacs.d/dotEmacs/isend-mode.el")
3:  (require 'isend)
4:  (setq isend-skip-empty-lines nil)
5:  (setq isend-strip-empty-lines nil)
6:  (setq isend-delete-indentation t)
7:  (setq isend-end-with-empty-line t)

Note Please replace “~/.emacs.d/dotEmacs/isend-mode.el” with the path of
isend-mode on your machine.

Now in Emacs, press M-x isend-associate and type in the destination buffer,
for example, *shell*, then select the code block you want to send and press
C-Enter to send it to the Python interpreter in *shell*.

3 Defining Advice for isend

But there is another annoying problem, isend only sends the code to a
specified buffer, but the code won’t be executed automatically.

With the following advice (with an advice you could run some
commands before/after a specified function is invoked), after sending code,
Emacs will switch to the destination buffer, execute the code that is sent
and switch back to the original buffer.

1:  (defadvice isend-send (after advice-run-code-sent activate compile)
2:    "Execute whatever sent to the (Python) buffer"
3:    (interactive)
4:    (let ((old-buf (buffer-name)))
5:      (progn
6:        (switch-to-buffer isend--command-buffer)
7:        (goto-char (point-max))
8:        (comint-send-input)
9:        (switch-to-buffer old-buf))))

Note Variable `isend–command-buffer’ is used here. I made it global
because I defined this advice in my init.el.

4 Test

In the first section, the program paused and entered PDB when variable i was
equal to 6:

1:  9 - 7
2:  8 - 6
3:  7 - 5
4:  > /home/wenshan/test.py(13)<module>()
5:       11     for i in xrange(9, 1, -1):
6:       12         if i == 6: import ipdb; ipdb.set_trace()
7:  ---> 13         print f(i, i - 2)
8:  
9:  ipdb>

Press C-c i to get into the interactive Python interpreter:

1:       12         if i == 6: import ipdb; ipdb.set_trace()
2:  ---> 13         print f(i, i - 2)
3:  
4:  ipdb> !import code; code.interact(local=vars())
5:  Python 2.7.3 (default, Jan  2 2013, 16:53:07)
6:  [GCC 4.7.2] on linux2
7:  Type "help", "copyright", "credits" or "license" for more information.
8:  (InteractiveConsole)
9:  In :

After that, associate isend with the shell we opened: M-x isend-associate RET *shell* RET

Invoke isend-mode in any python-mode buffer: M-x isend-mode , then write
some code such as redefining the f() method:

1:  def f(a, b):
2:      print "汝不知夫螳螂乎,怒其臂以当履带。 ——《庄子·查水表》"

Select the newly defined f() and press C-RET to send it to the Python
interpreter in *shell* :

 1:  ipdb> !import code; code.interact(local=vars())
 2:  Python 2.7.3 (default, Jan  2 2013, 16:53:07)
 3:  [GCC 4.7.2] on linux2
 4:  Type "help", "copyright", "credits" or "license" for more information.
 5:  (InteractiveConsole)
 6:  In : def f(a, b):
 7:      print "汝不知夫螳螂乎,怒其臂以当履带。 ——《庄子·查水表》"
 8:  
 9:  
10:  ...: ...: In : In :

Now, if you evaluate f(1, 2) in *shell* you will find that the original f()
has been overridden.

1:  In : f(1, 2)
2:  汝不知夫螳螂乎,怒其臂以当履带。 ——《庄子·查水表》

Press C-d in *shell* to exit the interactive interpreter and go back to
PDB. Enter c to continue the program and you can see the f() method is
still overridden.

1:  In :
2:  ipdb> c
3:  汝不知夫螳螂乎,怒其臂以当履带。 ——《庄子·查水表》
4:  .
5:  .
6:  .

Done.

Happy Hacking!

Note: English is not my first language, so please feel free to point out any
mistakes you may find.


Date: 2013-06-13

Author: 任文山 (Ren Wenshan)

Org version 7.9.3d with Emacs version 24

Validate XHTML 1.0

3 thoughts on “Emacs: Python Debugging, Send Code to an Arbitrary Buffer

  1. Pingback: Starred Links – June 2013 | 肉山博客 (Wenshan's Blog)

Leave a Reply