Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOC: add guide on shared docstrings #20016

Merged
merged 6 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fillna is actually not a really good example, as it has an example section only targetting DataFrame .. (but anyhow, that is another issue :))

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 @@ -3341,7 +3341,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