pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

[FEAT] Generate docstrings that are compatible with Sphinx and stubgen

Open AWhetter opened this issue 4 years ago • 1 comments

This issue follows on from a discussion at https://github.com/python/mypy/issues/9070

Current Behaviour

Currently, when a function is overloaded in pybind, the docstring generated includes a single, generic type signature at the top (name(*args, **kwargs) -> Any). Sphinx autodoc will document the signature of the function using this generic type signature (https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#confval-autodoc_docstring_signature). However it recently added support for reading multiple type signatures from the docstring (https://github.com/sphinx-doc/sphinx/issues/2106). This is apparently an already established convention and it's used in CPython source code (https://github.com/python/cpython/blob/c0f22fb8b3006936757cebb959cee94e285bc503/Objects/rangeobject.c#L156). Furthermore, stubgen (https://mypy.readthedocs.io/en/stable/stubgen.html) uses this generic function signature, which makes the type stubs that it generates useless (this was the original issue discussed in https://github.com/python/mypy/issues/9070).

New Behaviour

This issue is a feature request to prepend docstrings with all of the overload signatures of the function (unless options.disable_function_signatures() has been called).

To use an example from the pybind11 documentation:

py::class_<Pet>(m, "Pet")
   .def(py::init<const std::string &, int>())
   .def("set", (void (Pet::*)(int)) &Pet::set, "Set the pet's age")
   .def("set", (void (Pet::*)(const std::string &)) &Pet::set, "Set the pet's name");

This produces docstrings that looks like the following:

>>> help(example.Pet)

class Pet(__builtin__.object)
 |  Methods defined here:
 |
 |  __init__(...)
 |      __init__(self: Pet, arg0: str, arg1: int) -> None
 |
 |  set(...)
 |      set(*args, **kwargs) -> Any
 |      Overloaded function.
 |
 |      1. set(self: Pet, arg0: int) -> None
 |
 |      Set the pet's age
 |
 |      2. set(self: Pet, arg0: str) -> None
 |
 |      Set the pet's name

This request is to have the docstrings instead look like the following:

>>> help(example.Pet)

class Pet(__builtin__.object)
 |  Methods defined here:
 |
 |  __init__(...)
 |      __init__(self: Pet, arg0: str, arg1: int) -> None
 |
 |  set(...)
 |      set(self: Pet, arg0: int) -> None
 |      set(self: Pet, arg0: str) -> None
 |      Overloaded function.
 |
 |      1. set(self: Pet, arg0: int) -> None
 |
 |      Set the pet's age
 |
 |      2. set(self: Pet, arg0: str) -> None
 |
 |      Set the pet's name

Furthermore, I think it needs to be possible to have all of the function signatures at the start of the docstring generated but without the section headings. This would allow a user to write the whole docstring as one, but still have the function signatures generated for Sphinx and/or stubgen.

So it should be possible to generate a docstring like this:

>>> help(example.Pet.set)

set(...)
 |      set(self: Pet, arg0: int) -> None
 |      set(self: Pet, arg0: str) -> None
 |      Set attributes on the pet.
 |
 |      When arg0 is an integer, the pet's age is set.
 |      When arg0 is a string, the pet's name is set.

Implementation Details

I think the second change, to generate the signatures without signature headings, would require the addition of a new option. To maintain current behaviour, the options.disable_function_signatures() function could still turn all signatures off. A new option disable_signature_headings() would turn only the section headings off.

So the following code would produce the docstring seen above:

py::options options;
options.disable_signature_headings();

py::class_<Pet>(m, "Pet")
   .def(py::init<const std::string &, int>())
   .def("set", (void (Pet::*)(int)) &Pet::set)
   .def("set", (void (Pet::*)(const std::string &)) &Pet::set, "Set attributes on the pet.\n\nWhen arg0 is an integer, the pet's age is set.\nWhen arg0 is a string, the pet's name is set.");

AWhetter avatar Oct 26 '20 00:10 AWhetter

Maybe a rework of docstrings for overloaded functions could also include a fix for #3037?

ka-bo avatar Jun 16 '21 07:06 ka-bo