Skip to content

DOC: add guide on shared docstrings #20016

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

Merged
merged 6 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
73 changes: 73 additions & 0 deletions doc/source/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a ref here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do. Going to wait till the docstring guide is merged and I move it to that document so that the name makes sense.

==================

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this line is not long enough

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``
Copy link
Member

Choose a reason for hiding this comment

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

filled -> are filled

decorator. Finally, docstrings can be appended to with the ``Appender``
Copy link
Contributor

Choose a reason for hiding this comment

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

can we reference the Substituion/Appender via links to the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK, there isn't a good way to link to a specific function. I think you can only do a specific line (with or without a commit). If we pin the commit things may go out of date. If don't do a specific commit then the line number will become incorrect.

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
Copy link
Contributor

Choose a reason for hiding this comment

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

we'll -> we will, don't use contractions :>

``pandas.core.series.Series`` and ``pandas.core.frame.DataFrame``). We'll
substitute the children's class names in this docstring.
Copy link
Contributor

Choose a reason for hiding this comment

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

childrens'

Copy link
Contributor Author

@TomAugspurger TomAugspurger Mar 9, 2018

Choose a reason for hiding this comment

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

Children is a plural noun not ending in "s", so children's is the correct plural possessive.

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm, yeah never get these right :>. We'll -> we will


.. 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
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we use * rather than numbering generally (though this is slightly more clear)

initially empty.
2. Python decorators are applied inside out. So the order is Append then
Copy link
Contributor

Choose a reason for hiding this comment

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

double backticks on Append/Substitution

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.
Copy link
Member

Choose a reason for hiding this comment

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

I don't fully understand this reasoning (both with using % or Substitution you can write a normal docstring and append it with baseclass.method.__doc__ ?)


See ``pandas.core.generic.NDFrame.fillna`` for an example template, and
Copy link
Contributor

Choose a reason for hiding this comment

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

can you point to actual code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same issue with linking specific lines / commits, though maybe this one is OK, since we're giving an example.

But unfortunately I can't link to the correct version, since the correction is in this PR :)

``pandas.core.series.Series.fillna`` and ``pandas.core.generic.frame.fillna``
for the filled versions.

.. _Gitter: https://gitter.im/pydata/pandas
3 changes: 2 additions & 1 deletion pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 4 additions & 6 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

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 @@ -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,
Expand Down