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 6 commits into from Mar 28, 2018
Changes from all commits
File filter

Filter by extension

Filter by extension

Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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:
@@ -82,6 +82,9 @@ about reStructuredText can be found in:
- `Quick reStructuredText reference <>`_
- `Full reStructuredText specification <>`_

Pandas has some helpers for sharing docstrings between related classes, see

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

@@ -916,3 +919,79 @@ plot will be generated automatically when building the documentation.
>>> s.plot()
.. _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``

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):
def my_function(self):
class ChildB(Parent):
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

@jorisvandenbossche jorisvandenbossche Mar 12, 2018

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 :))

@@ -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)
def fillna(self, value=None, method=None, axis=None, inplace=False,
limit=None, downcast=None, **kwargs):
return super(DataFrame,
@@ -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
@@ -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)

@@ -31,7 +31,7 @@
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(
@@ -1254,7 +1254,8 @@ def transpose(self, *args, **kwargs):

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

@Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
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,
@@ -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)
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,