12

I'm looking for an elisp package that automatically inserts Python docstring for a method. I found a package, which is very close to my purpose. But it's in reStructured text, not in Google style.

sphinx-doc.el https://github.com/naiquevin/sphinx-doc.el

Describing arguments in docstrings (Google python style guide) https://www.chromium.org/chromium-os/python-style-guidelines#TOC-Describing-arguments-in-docstrings

My expectation is when I call M-x sphinx-doc-google within the following function,

def some_function(a, b, c):

I need a result like this.

def some_function(a, b, c):
    """
    Args:
        a:
        b:
        c:
    Returns:
    """

I know it's not difficult to implement by myself. I just want to ask this question to avoid the reinvention.

2
  • I don't think there is. This style isn't exactly popular in the larger Python community as far as I know.
    – user227
    Commented Jan 11, 2016 at 10:13
  • 1
    Thanks. I thought it is popular because the default setting of automatic docstring insertion rule of PyCharm is the Google style. I had used reStructured text for a while, but it's not very human readable. :(
    – sy2
    Commented Jan 11, 2016 at 20:53

4 Answers 4

12

Package

The code described in the following section have now been made available in a separate package. Please see this repository for details: https://github.com/Xaldew/yasnippet-radical-snippets.


Old Answer

I use the package called yasnippet for something similar to this. After some minor changes I adapted it to use the the Google docstring style instead:

Google styled Python yasnippet

Do note however that it requires some setup:

The snippet itself needs to execute some utility elisp code to generate the text. This is typically solved by creating a file called .yas-setup.el with the code inside the python-mode snippet directory. It is however also possible to place the code somewhere inside your .emacs instead.

The code for the snippet is:

# -*- mode: snippet -*-
# Insert Google style docstring and function definition.
# name: Python Google style Docstring
# key: defg
# type: snippet
# contributor: Xaldew
# --
def ${1:name}($2):
    \"\"\"$3
    ${2:$(python-args-to-google-docstring yas-text t)}
    ${5:Returns:
        $6
}
    \"\"\"
    ${0:$$(let ((beg yas-snippet-beg)
                (end yas-snippet-end))
        (yas-expand-snippet
          (buffer-substring-no-properties beg end) beg end
              (quote ((yas-indent-line nil) (yas-wrap-around-region nil))))
            (delete-trailing-whitespace beg (- end 1)))}

The code for the .yas-setup.el is:

(defun python-args-to-google-docstring (text &optional make-fields)
  "Return a reST docstring format for the python arguments in yas-text."
  (let* ((indent (concat "\n" (make-string (current-column) 32)))
         (args (python-split-args text))
     (nr 0)
         (formatted-args
      (mapconcat
       (lambda (x)
         (concat "   " (nth 0 x)
             (if make-fields (format " ${%d:arg%d}" (cl-incf nr) nr))
             (if (nth 1 x) (concat " \(default " (nth 1 x) "\)"))))
       args
       indent)))
    (unless (string= formatted-args "")
      (concat
       (mapconcat 'identity
          (list "" "Args:" formatted-args)
          indent)
       "\n"))))

Note that python-split-args is provided by the standard snippets. I.e.: https://github.com/AndreaCrotti/yasnippet-snippets/tree/master You do however get those by default when you install the package through package.el.

With everything setup properly, you should be able to write "defg" followed by Tab to expand the snippet (See the image for an example).

There is still an issue with using this inside nested indentation, e.g., within classes or as nested functions. In those cases the docstring is erroneously indented an extra time for some reason. I'll update this post if I manage to fix that.

The snippet should now work inside other scopes by forbidding yasnippet from auto-indenting the second expansion.

14
  • 2
    Stupid question, but how do I actually make this work on an existing function? I type defg and it gives me a new function named name with no arguments, and I can't see any way to automate it updating the docstring as I change that function. When I look at my Messages buffer, I see yas--update-mirrors: Wrong type argument: stringp, (python-args-to-google-docstring).
    – Translunar
    Commented Oct 12, 2017 at 14:48
  • 1
    I actually encountered this today as well in another of my snippets, I think it may be a bug in yasnippet. I'll have to create a minimal example to properly report it though. It may also be that chaining snippets in this fashion isn't supported anymore, but I hope that's not it.
    – Xaldew
    Commented Oct 13, 2017 at 16:04
  • Is this still an issue? I'm no longer able to replicate the above error using the latest Emacs/yasnippet.
    – Xaldew
    Commented Nov 16, 2017 at 10:33
  • Yeah, It's still problematic. I'm using emacs 24.5.1 (the latest Ubuntu version) and the latest version of yasnippet.
    – Translunar
    Commented Nov 17, 2017 at 18:55
  • 1
    @AstroFloyd That is correct, the code for .yas-setup.el should end up in the same directory as the snippet directory for the currently active mode. Which is ~/.emacs.d/snippets/python-mode/.yas-setup.el for python-mode as you pointed out.
    – Xaldew
    Commented Oct 8, 2019 at 8:20
3

As lunaryorn mentioned that style is not popular and there aren't any packages.

However there is a package called sphinx-doc which will generate doc string in sphinx format(demo).

You can modify that package to generate strings as per your requirement.

0

I tried to use Xaldev's answer, but it seems it doesn't work with type-annotations.

So, I wrote this snippet. It contains python's script to analyze defs and generate documentation and some elisp's code, which you should add to your user-config part.

disclaimer: code is quite dirty, but for me it generates such a pleacant docs:

def test(region: str = "foo", bar: t.List[t.Any] = [1, 2],
         baz: int = 0) -> None:
    """

    Args:
        region (str): (default: 'foo')
        bar (t.List[t.Any]): (default: [1, 2])
        baz (int): (default: 0)
    Returns:
        None: nothing
    """

    def __init__(self) -> None:
        """Initialize object

        Returns:
            None: nothing
        """
        ...
    ...

You're welcome to try and change it for your purposes.

-1

You can use this code.

Move the cursor on your function name and then F9.

 (defun chomp (str)
        "Chomp leading and tailing whitespace from STR."
        (let ((s (if (symbolp str) (symbol-name str) str)))
          (replace-regexp-in-string
           "\\(^[[:space:]\n]*\\|[[:space:]\n]*$\\)" "" s)))
 (defun get-function-definition(sentence)
    (if (string-match "def.*(.*):" sentence)
        (match-string 0 sentence)))
 (defun get-parameters(sentence)
    (setq y (get-function-definition sentence))
    (if y
        (if (string-match "(.*)" y)
            (match-string 0 y))))
 (autoload 'thing-at-point "thingatpt" nil t) ;; build-in librairie
 (defun python-insert-docstring()
        (interactive)
        (setq p (get-parameters (thing-at-point 'sentence)))
        (forward-line 1)
        (insert "    \"\"\"\n")
        (insert "\tArgs:\n")
        (setq params (split-string p "[?\,?\(?\)?\ ]"))
        (while params
          (if (/= (length (chomp (car params))) 0)
              (progn
                (insert "        ")
                (insert (chomp (car params)))
                (insert ": \n")))
          (setq params (cdr params)))
        (insert "    Returns:\n    \"\"\"\n"))
      (global-set-key (kbd "<f9>") 'python-insert-docstring)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.