sphinx-contrib / napoleon

Other
149 stars 48 forks source link

Inconsistent formatting of "Parameters" and "Returns" sections for various projects published on readthedocs #17

Closed lukelbd closed 4 years ago

lukelbd commented 4 years ago

Docstring

I have a docstring that looks like the following:

"""
Parameters
----------
axis : `~matplotlib.axis.Axis`
    The axis, required for compatibility reasons.
a : float, optional
    The base of the exponential, i.e. the :math:`a` in :math:`Ca^{bx}`.
b : float, optional
    The scale for the exponent, i.e. the :math:`b` in :math:`Ca^{bx}`.
c : float, optional
    The coefficient of the exponential, i.e. the :math:`C`
    in :math:`Ca^{bx}`.
minpos : float, optional
    The minimum permissible value, used to truncate negative values.
inverse : bool, optional
    If ``True``, the "forward" direction performs the inverse operation.
"""

Parameters table: local vs. readthedocs

When I run make html on my computer, my parameter tables look like this:

Screen Shot 2019-11-16 at 1 13 36 PM

The corresponding HTML code looks like:

<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>axis</strong> (<a class="reference external" href="https://matplotlib.org/api/axis_api.html#matplotlib.axis.Axis" title="(in Matplotlib v3.1.1)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">Axis</span></code></a>) – The axis, required for compatibility reasons.</p></li>
<li><p><strong>a</strong> (<em>float, optional</em>) – The base of the exponential, i.e. the <span class="math notranslate nohighlight">\(a\)</span> in <span class="math notranslate nohighlight">\(Ca^{bx}\)</span>.</p></li>
<li><p><strong>b</strong> (<em>float, optional</em>) – The scale for the exponent, i.e. the <span class="math notranslate nohighlight">\(b\)</span> in <span class="math notranslate nohighlight">\(Ca^{bx}\)</span>.</p></li>
<li><p><strong>c</strong> (<em>float, optional</em>) – The coefficient of the exponential, i.e. the <span class="math notranslate nohighlight">\(C\)</span>
in <span class="math notranslate nohighlight">\(Ca^{bx}\)</span>.</p></li>
<li><p><strong>minpos</strong> (<em>float, optional</em>) – The minimum permissible value, used to truncate negative values.</p></li>
<li><p><strong>inverse</strong> (<em>bool, optional</em>) – If <code class="docutils literal notranslate"><span class="pre">True</span></code>, the “forward” direction performs the inverse operation.</p></li>
</ul>
</dd>
</dl>

There is a "section header" with a gray background, and the font is normal sized.

When readthedocs builds my documentation, it looks like this:

Screen Shot 2019-11-16 at 1 13 42 PM

The HTML code looks like this:

<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body"><ul class="first last simple">
<li><strong>axis</strong> (<a class="reference external" href="https://matplotlib.org/api/axis_api.html#matplotlib.axis.Axis" title="(in Matplotlib v3.1.1)"><code class="xref py py-obj docutils literal notranslate"><span class="pre">Axis</span></code></a>) – The axis, required for compatibility reasons.</li>
<li><strong>a</strong> (<em>float, optional</em>) – The base of the exponential, i.e. the <span class="math notranslate nohighlight">\(a\)</span> in <span class="math notranslate nohighlight">\(Ca^{bx}\)</span>.</li>
<li><strong>b</strong> (<em>float, optional</em>) – The scale for the exponent, i.e. the <span class="math notranslate nohighlight">\(b\)</span> in <span class="math notranslate nohighlight">\(Ca^{bx}\)</span>.</li>
<li><strong>c</strong> (<em>float, optional</em>) – The coefficient of the exponential, i.e. the <span class="math notranslate nohighlight">\(C\)</span>
in <span class="math notranslate nohighlight">\(Ca^{bx}\)</span>.</li>
<li><strong>minpos</strong> (<em>float, optional</em>) – The minimum permissible value, used to truncate negative values.</li>
<li><strong>inverse</strong> (<em>bool, optional</em>) – If <code class="docutils literal notranslate"><span class="pre">True</span></code>, the “forward” direction performs the inverse operation.</li>
</ul>
</td>
</tr>
</tbody>
</table>

Instead, the parameters and options are in seperate columns and the font is much smaller. Note that with this format, I also have a lot of trouble embedding RST tables in the parameters section, because they do not have scroll bars and they bleed into the margin (see #18).

Examples from other published projects

Also, various other projects that use readthedocs generally look like the first example:

Screen Shot 2019-11-16 at 1 18 47 PM Screen Shot 2019-11-16 at 1 17 30 PM

Summary

What could be causing this inconsistency? Which version is "correct"? The second example seems more correct to me (the gray headers look kind of weird), but it if that's the case, various other projects are suffering from the same problem.

lukelbd commented 4 years ago

After some investigation I think this has something to do with how the :parameters: role is interpreted by the different sphinx versions.

It seems that older versions translate the :parameters: role into a standard 2-column table (readthedocs default is sphinx 1.8.5) while newer versions translate the :parameters: items into exactly like :param: items roles (local version is sphinx 2.2.0). In my conf.py I had napoleon_use_param set to False, i.e. I instructed napoleon to use :parameters:, which caused this inconsistent behavior.

I now see that the gray section header-style is the default / "correct" behavior, so from now on I'll set napoleon_use_param to True. I used to like the smaller font size of the "incorrect" version, but since I now use page stubs for all of my class methods instead of concatenating them onto one page (thanks to a fork of automodapi), the larger font size is no issue.

lukelbd commented 4 years ago

P.S. Navigating to lib/python3.7/site-packages/sphinx/ext/napoleon/docstring.py and replacing _parse_parameters_section with

def _parse_parameters_section(self, section: str) -> List[str]:
    fields = self._consume_fields()
    if self._config.napoleon_use_param:
        string = self._format_docutils_params(fields)
    else:
        string = self._format_fields(_('Parameters'), fields)
    print(self._config.napoleon_use_param, 'section:')
    print('\n'.join(string))
    return string

confirmed that the napoleon_use_param setting in conf.py was indeed being respected.