From b4850dacea3ee3a1a43008cfb0f190b90522ac9c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 28 Mar 2018 02:49:47 -0500 Subject: [PATCH] DOC: add guide on shared docstrings (#20016) --- doc/source/contributing.rst | 1 - doc/source/contributing_docstring.rst | 79 +++++++++++++++++++++++++++ pandas/core/frame.py | 3 +- pandas/core/generic.py | 10 ++-- pandas/core/panel.py | 5 +- pandas/core/series.py | 3 +- 6 files changed, 90 insertions(+), 11 deletions(-) diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index ff0aa8af611db3..967d1fe3369f0c 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -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 diff --git a/doc/source/contributing_docstring.rst b/doc/source/contributing_docstring.rst index c210bb7050fb84..f80bfd92537645 100644 --- a/doc/source/contributing_docstring.rst +++ b/doc/source/contributing_docstring.rst @@ -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 +:ref:`docstring.sharing`. + 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() """ 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. diff --git a/pandas/core/frame.py b/pandas/core/frame.py index d2617305d220a9..ace975385ce321 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -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, diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 80885fd9ef1392..f1fa43818ce64d 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -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 @@ -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) diff --git a/pandas/core/panel.py b/pandas/core/panel.py index 5bb4b72a0562d9..7c087ac7deafcc 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -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( @@ -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, diff --git a/pandas/core/series.py b/pandas/core/series.py index 48e6453e364911..62f0ea3ce8b2a5 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -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,