Sphinx: Sphinx pulls docstring from super class if __init__ is not defined

Created on 4 Jun 2020  路  6Comments  路  Source: sphinx-doc/sphinx

Describe the bug

In https://github.com/pygae/galgebra/blob/master/galgebra/atoms.py , class BasisVectorSymbol inherits class Symbol from https://github.com/sympy/sympy . When the option inherited-members is set to False, the doc of galgebra.atoms.BasisVectorSymbol renders to:

image

See https://galgebra.readthedocs.io/en/latest/generated/galgebra.atoms.html#galgebra.atoms.BasisVectorSymbol

Observation 1: The docstring of class Symbol is rendered despite that inherited-members is set to False
Observation 2: The docstring is rendered incorrectly, namely the last three lines merged into one and break out of the highlighted code area, might be caused by swallowing some whitespaces.

See further discussions in https://github.com/pygae/galgebra/pull/401#issuecomment-634138381

To Reproduce
Steps to reproduce the behavior:

See the above

Expected behavior

Condition:

  1. option inherited-members is set to False
  2. __init__ is not defined for the subclasss
  3. the super class has docstring

Expected Result:

No docstring for __init__ of the subclass is rendered.

Your project

https://github.com/pygae/galgebra/

Screenshots

See above

Environment info

  • OS: Linux
  • Python version: 3.7.3
  • Sphinx version: Sphinx-1.8.5
  • Sphinx extensions:
extensions = [
    'nbsphinx',
    'nbsphinx_link',
    'sphinx.ext.autodoc',
    'sphinx.ext.autosummary',
    'sphinx.ext.intersphinx',
    'sphinx.ext.coverage',
    'sphinx.ext.napoleon',
    'sphinx.ext.mathjax',
    'sphinx.ext.viewcode',
    'IPython.sphinxext.ipython_directive',
    'IPython.sphinxext.ipython_console_highlighting',
    'sphinx_markdown_tables',
    'releases',
    'sphinxcontrib.bibtex',

    # local extensions
    'md_include',
]
  • Extra tools: No

Additional context
Add any other context about the problem here.

  • [e.g. URL or Ticket]
autodoc question

Most helpful comment

It seems your project uses autoclass_content = 'both'. With the setting, autodoc uses docstrings of both class and __init__() (or __new__()) method to generate a document of the class. And :inherited-members: does not effect that. It only effects to filter the documented members of the module or class.

Observation 2: The docstring is rendered incorrectly, namely the last three lines merged into one and break out of the highlighted code area, might be caused by swallowing some whitespaces.

The docstring of sympy.Symbol.__new__() is here:
https://github.com/sympy/sympy/blob/25fbcce5b1a4c7e3956e6062930f4a44ce95a632/sympy/core/symbol.py#L223-L232

And it is extracted as below viainspect.getdoc():

Symbols are identified by name and assumptions::

>>> from sympy import Symbol
>>> Symbol("x") == Symbol("x")
True
>>> Symbol("x", real=True) == Symbol("x", real=False)
False

As a result, the code block is not indented as expected. So it is not treated as code-block.

All 6 comments

It seems your project uses autoclass_content = 'both'. With the setting, autodoc uses docstrings of both class and __init__() (or __new__()) method to generate a document of the class. And :inherited-members: does not effect that. It only effects to filter the documented members of the module or class.

Observation 2: The docstring is rendered incorrectly, namely the last three lines merged into one and break out of the highlighted code area, might be caused by swallowing some whitespaces.

The docstring of sympy.Symbol.__new__() is here:
https://github.com/sympy/sympy/blob/25fbcce5b1a4c7e3956e6062930f4a44ce95a632/sympy/core/symbol.py#L223-L232

And it is extracted as below viainspect.getdoc():

Symbols are identified by name and assumptions::

>>> from sympy import Symbol
>>> Symbol("x") == Symbol("x")
True
>>> Symbol("x", real=True) == Symbol("x", real=False)
False

As a result, the code block is not indented as expected. So it is not treated as code-block.

There is nothing to do from Sphinx side. closing.

As a result, the code block is not indented as expected. So it is not treated as code-block.

Why is half of it treated as a code block?

It is recognized as "quoted-literal-blocks". See https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#quoted-literal-blocks.
The reST parser considers the first two lines of the block starting with ">>>" as a quoted-literal-block. And next line, "True" interrupts the block and starts a new paragraph. So remaining lines are considered as a paragraph.

BTW, you need to insert a line break to the first sentence and make the codes indented, to mark up this expectedly.

# insert a line break before ::
"""Symbols are identified by name and assumptions
::

   >>> from sympy import Symbol
   >>> Symbol("x") == Symbol("x")
   True
   >>> Symbol("x", real=True) == Symbol("x", real=False)
   False
"""

You can see how the docstring is recognized to autodoc via textwrap.dedent().

So perhaps the real issue here is that the sympy docstrings are using the numpydoc convention which _does_ allow code samples to be unindented; while galgebra is not using numpydoc.

No, this is not related to numpydoc. inspect.cleandoc() removes indentations using the baseline based on the second (or following) line.

>>> def foo():
...     """blah blah blah
...
...            this is indented block.
...     """
...
>>> inspect.cleandoc(foo.__doc__)
'blah blah blah\n\nthis is indented block.'
>>> def bar():
...     """blah blah blah
...
...        blah blah blah
...            this is indented block.
...     """
...
>>> inspect.cleandoc(bar.__doc__)
'blah blah blah\n\nblah blah blah\n    this is indented block.'

This means we can't put the marker of the code-block (::) to the first line of the docstring because the docstring processor removes the indentation of the following block on clean up. To do that, we have to put the marker on the second line (as I commented above).

Was this page helpful?
0 / 5 - 0 ratings