From 0e43660d96d4f4d8f860643fc1271a603ad2390c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 5 Mar 2018 08:21:33 -0600 Subject: [PATCH 1/3] DOC: Explain shared docs guide --- doc/source/contributing.rst | 73 +++++++++++++++++++++++++++++++++++++ pandas/core/frame.py | 3 +- pandas/core/generic.py | 10 ++--- pandas/core/panel.py | 5 ++- pandas/core/series.py | 3 +- 5 files changed, 84 insertions(+), 10 deletions(-) diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index e159af9958fde..1e7bdf22557d3 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -1077,5 +1077,78 @@ The branch will still exist on GitHub, so to delete it there do:: git push origin --delete shiny-new-feature +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. .. _Gitter: https://gitter.im/pydata/pandas diff --git a/pandas/core/frame.py b/pandas/core/frame.py index a66d00fff9714..2ad9e07334232 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -3119,7 +3119,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 a45887543dc53..5d3cdaea4119f 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -4608,7 +4608,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 @@ -4699,11 +4701,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 052d555df76f1..3193022060155 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 069f0372ab6e1..a7e952ac4a288 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2660,7 +2660,8 @@ def rename(self, index=None, **kwargs): def reindex(self, index=None, **kwargs): return super(Series, self).reindex(index=index, **kwargs) - @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, From b040ab1d8d2099e889be981205feb3742847f71d Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 12 Mar 2018 12:52:49 -0500 Subject: [PATCH 2/3] Moved to docstring --- doc/source/contributing.rst | 74 ------------------------- doc/source/contributing_docstring.rst | 79 +++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 74 deletions(-) diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index 761af822e2d1d..c7d16ba46e221 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -1077,78 +1077,4 @@ The branch will still exist on GitHub, so to delete it there do:: git push origin --delete shiny-new-feature -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. - .. _Gitter: https://gitter.im/pydata/pandas diff --git a/doc/source/contributing_docstring.rst b/doc/source/contributing_docstring.rst index cd56b76fa891b..5c42819bffb60 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. @@ -908,3 +911,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. From b6512b5388e5f4bb41906a919a3d74e1607f4c2d Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 26 Mar 2018 11:09:26 -0500 Subject: [PATCH 3/3] Fixup whitespace --- doc/source/contributing_docstring.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/contributing_docstring.rst b/doc/source/contributing_docstring.rst index 5db8c814e0bdc..f80bfd9253764 100644 --- a/doc/source/contributing_docstring.rst +++ b/doc/source/contributing_docstring.rst @@ -945,7 +945,7 @@ substitute the children's class names in this docstring. def my_function(self): """Apply my function to %(klass)s.""" ... - + class ChildA(Parent): @Substitution(klass="ChildA") @Appender(Parent.my_function.__doc__) @@ -957,7 +957,7 @@ substitute the children's class names in this docstring. @Appender(Parent.my_function.__doc__) def my_function(self): ... - + The resulting docstrings are .. code-block:: python