Tibor's Musings

Writing Python Docstrings with Emacs

Writing documentation alongside coding is one of the best ways to ensure that the documentation stays up to date with the code. In Python, such a documentation can be achieved by writing rich docstrings. How can Emacs help us with writing rich docstrings?

Code skeleton and templates

Emacs offers several packages providing "code skeletons" or "code templates" that help with writing repetitive patterns. For example, if you type def in a Python buffer and press TAB afterwards, the editor can auto-complete basic generic function skeleton for you; including skeleton docstring.

There are several alternatives how to achieve skeletons or templating, out of which I've settled for yasnippets. Yasnippets come with predefined support for many languages. In our example at hand, we'd like it to generate rich, appropriate docstrings when writing Python functions or class methods.

Epytext

One popular choice how to richly format Python docstrings is to use the epytext markup, after which epydoc can generate nicely formatted static documentation pages for the code.

A function documented in the epytext markup looks like this:

def area(length, width):
    """
    Return rectangle area.

    @param length: rectangle length
    @type length: float
    @param width: rectangle width
    @type width: float
    @return: rectangle area
    @rtype: float
    """
    return length*width

Notice how @param serves to document function parameters, @type their types, @return what the function returns, and @rtype its type. Other useful markup is @raise to document which exceptions might be raised by the function, or @note to add extra notes.

Yasnippet's native def snippet does not support epytext markup. It is easy to define a new custom snippet (say called de) that will support it.

Defining custom snippet

Here is how to load custom snippets from some directory:

(require 'yasnippet)
(yas/initialize)
(yas/load-directory "~/.emacs.d/snippets")

Here is how I defined custom de snippet, located in ~/.emacs.d/snippets/text-mode/python-mode/de:

# -*- coding: utf-8 -*-
# name: de
# contributor: Orestis Markou
# contributor: Yasser González Fernández <yglez@uh.cu>
# contributor: Tibor Simko <tibor.simko@cern.ch>
# --
def ${1:name}($2):
    """
    $3
    ${2:$
    (let* ((indent
            (concat "\n" (make-string (current-column) 32)))
           (args
            (mapconcat
             '(lambda (x)
                (if (not (string= (nth 0 x) ""))
                    (concat "@param " (nth 0 x) ": " indent
                            "@type " (nth 0 x) ": ")))
             (mapcar
              '(lambda (x)
                 (mapcar
                  '(lambda (x)
                     (replace-regexp-in-string "[[:blank:]]*$" ""
                      (replace-regexp-in-string "^[[:blank:]]*" "" x)))
                  x))
              (mapcar '(lambda (x) (split-string x "="))
                      (split-string text ",")))
             indent)))
      (if (string= args "")
          (concat indent "@return: " indent "@rtype: " indent (make-string 3 34))
        (mapconcat
         'identity
         (list "" args "@return: " "@rtype: " (make-string 3 34))
         indent)))
    }
    $0

Now, when typing de RET, Emacs will substitute function skeleton and will pre-complete docstring with epytext friendly formatting as we shall be typing function parameters.

Live demo!

Emacs Epydoc Snippets

Conclusions?

Writing up-to-date, feature-rich code documentation can be both easy and fun provided one uses powerful extensible editors.