Skip to content

Commit

Permalink
DOC: add guide on shared docstrings (pandas-dev#20016)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAugspurger authored and Andrew Bui committed Apr 2, 2018
1 parent a303326 commit b4850da
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 11 deletions.
1 change: 0 additions & 1 deletion doc/source/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1088,5 +1088,4 @@ The branch will still exist on GitHub, so to delete it there do::
git push origin --delete shiny-new-feature
.. _Gitter: https://gitter.im/pydata/pandas
79 changes: 79 additions & 0 deletions doc/source/contributing_docstring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ about reStructuredText can be found in:
- `Quick reStructuredText reference <http://docutils.sourceforge.net/docs/user/rst/quickref.html>`_
- `Full reStructuredText specification <http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>`_

Pandas has some helpers for sharing docstrings between related classes, see
:ref:`docstring.sharing`.

The rest of this document will summarize all the above guides, and will
provide additional convention specific to the pandas project.

Expand Down Expand Up @@ -916,3 +919,79 @@ plot will be generated automatically when building the documentation.
>>> s.plot()
"""
pass
.. _docstring.sharing:

Sharing Docstrings
------------------

Pandas has a system for sharing docstrings, with slight variations, between
classes. This helps us keep docstrings consistent, while keeping things clear
for the user reading. It comes at the cost of some complexity when writing.

Each shared docstring will have a base template with variables, like
``%(klass)s``. The variables filled in later on using the ``Substitution``
decorator. Finally, docstrings can be appended to with the ``Appender``
decorator.

In this example, we'll create a parent docstring normally (this is like
``pandas.core.generic.NDFrame``. Then we'll have two children (like
``pandas.core.series.Series`` and ``pandas.core.frame.DataFrame``). We'll
substitute the children's class names in this docstring.

.. code-block:: python
class Parent:
def my_function(self):
"""Apply my function to %(klass)s."""
...
class ChildA(Parent):
@Substitution(klass="ChildA")
@Appender(Parent.my_function.__doc__)
def my_function(self):
...
class ChildB(Parent):
@Substitution(klass="ChildB")
@Appender(Parent.my_function.__doc__)
def my_function(self):
...
The resulting docstrings are

.. code-block:: python
>>> print(Parent.my_function.__doc__)
Apply my function to %(klass)s.
>>> print(ChildA.my_function.__doc__)
Apply my function to ChildA.
>>> print(ChildB.my_function.__doc__)
Apply my function to ChildB.
Notice two things:

1. We "append" the parent docstring to the children docstrings, which are
initially empty.
2. Python decorators are applied inside out. So the order is Append then
Substitution, even though Substitution comes first in the file.

Our files will often contain a module-level ``_shared_doc_kwargs`` with some
common substitution values (things like ``klass``, ``axes``, etc).

You can substitute and append in one shot with something like

.. code-block:: python
@Appender(template % _shared_doc_kwargs)
def my_function(self):
...
where ``template`` may come from a module-level ``_shared_docs`` dictionary
mapping function names to docstrings. Wherever possible, we prefer using
``Appender`` and ``Substitution``, since the docstring-writing processes is
slightly closer to normal.

See ``pandas.core.generic.NDFrame.fillna`` for an example template, and
``pandas.core.series.Series.fillna`` and ``pandas.core.generic.frame.fillna``
for the filled versions.
3 changes: 2 additions & 1 deletion pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -3696,7 +3696,8 @@ def rename(self, *args, **kwargs):
kwargs.pop('mapper', None)
return super(DataFrame, self).rename(**kwargs)

@Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
@Substitution(**_shared_doc_kwargs)
@Appender(NDFrame.fillna.__doc__)
def fillna(self, value=None, method=None, axis=None, inplace=False,
limit=None, downcast=None, **kwargs):
return super(DataFrame,
Expand Down
10 changes: 4 additions & 6 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5252,7 +5252,9 @@ def infer_objects(self):
# ----------------------------------------------------------------------
# Filling NA's

_shared_docs['fillna'] = ("""
def fillna(self, value=None, method=None, axis=None, inplace=False,
limit=None, downcast=None):
"""
Fill NA/NaN values using the specified method
Parameters
Expand Down Expand Up @@ -5343,11 +5345,7 @@ def infer_objects(self):
1 3.0 4.0 NaN 1
2 NaN 1.0 NaN 5
3 NaN 3.0 NaN 4
""")

@Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
def fillna(self, value=None, method=None, axis=None, inplace=False,
limit=None, downcast=None):
"""
inplace = validate_bool_kwarg(inplace, 'inplace')
value, method = validate_fillna_kwargs(value, method)

Expand Down
5 changes: 3 additions & 2 deletions pandas/core/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
create_block_manager_from_blocks)
from pandas.core.series import Series
from pandas.core.reshape.util import cartesian_product
from pandas.util._decorators import Appender
from pandas.util._decorators import Appender, Substitution
from pandas.util._validators import validate_axis_style_args

_shared_doc_kwargs = dict(
Expand Down Expand Up @@ -1254,7 +1254,8 @@ def transpose(self, *args, **kwargs):

return super(Panel, self).transpose(*axes, **kwargs)

@Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
@Substitution(**_shared_doc_kwargs)
@Appender(NDFrame.fillna.__doc__)
def fillna(self, value=None, method=None, axis=None, inplace=False,
limit=None, downcast=None, **kwargs):
return super(Panel, self).fillna(value=value, method=method, axis=axis,
Expand Down
3 changes: 2 additions & 1 deletion pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -3384,7 +3384,8 @@ def drop(self, labels=None, axis=0, index=None, columns=None,
columns=columns, level=level,
inplace=inplace, errors=errors)

@Appender(generic._shared_docs['fillna'] % _shared_doc_kwargs)
@Substitution(**_shared_doc_kwargs)
@Appender(generic.NDFrame.fillna.__doc__)
def fillna(self, value=None, method=None, axis=None, inplace=False,
limit=None, downcast=None, **kwargs):
return super(Series, self).fillna(value=value, method=method,
Expand Down

0 comments on commit b4850da

Please sign in to comment.