From 06a5d9e33860b93a87c03dc753b01c4411c96d38 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Thu, 13 Feb 2020 21:19:03 -0500 Subject: [PATCH 01/16] CLN: @doc - base.py --- pandas/core/arrays/categorical.py | 6 +++--- pandas/core/base.py | 18 +++++++----------- pandas/core/indexes/datetimelike.py | 6 +++--- pandas/core/series.py | 3 +-- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 6c7c35e9b4763..0ebbb10e6e273 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -15,6 +15,7 @@ Substitution, cache_readonly, deprecate_kwarg, + doc, ) from pandas.util._validators import validate_bool_kwarg, validate_fillna_kwargs @@ -51,7 +52,7 @@ _extension_array_shared_docs, try_cast_to_ea, ) -from pandas.core.base import NoNewAttributesMixin, PandasObject, _shared_docs +from pandas.core.base import IndexOpsMixin, NoNewAttributesMixin, PandasObject import pandas.core.common as com from pandas.core.construction import array, extract_array, sanitize_array from pandas.core.indexers import check_array_indexer, deprecate_ndim_indexing @@ -1352,8 +1353,7 @@ def memory_usage(self, deep=False): """ return self._codes.nbytes + self.dtype.categories.memory_usage(deep=deep) - @Substitution(klass="Categorical") - @Appender(_shared_docs["searchsorted"]) + @doc(IndexOpsMixin.searchsorted, klass="Categorical") def searchsorted(self, value, side="left", sorter=None): # searchsorted is very performance sensitive. By converting codes # to same dtype as self.codes, we get much faster performance. diff --git a/pandas/core/base.py b/pandas/core/base.py index 56d3596f71813..4656275c8da89 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -13,7 +13,7 @@ from pandas.compat import PYPY from pandas.compat.numpy import function as nv from pandas.errors import AbstractMethodError -from pandas.util._decorators import Appender, Substitution, cache_readonly, doc +from pandas.util._decorators import cache_readonly, doc from pandas.util._validators import validate_bool_kwarg from pandas.core.dtypes.cast import is_nested_object @@ -1402,18 +1402,18 @@ def memory_usage(self, deep=False): def factorize(self, sort=False, na_sentinel=-1): return algorithms.factorize(self, sort=sort, na_sentinel=na_sentinel) - _shared_docs[ - "searchsorted" - ] = """ + @doc(klass="Index") + def searchsorted(self, value, side="left", sorter=None) -> np.ndarray: + """ Find indices where elements should be inserted to maintain order. - Find the indices into a sorted %(klass)s `self` such that, if the + Find the indices into a sorted {klass} `self` such that, if the corresponding elements in `value` were inserted before the indices, the order of `self` would be preserved. .. note:: - The %(klass)s *must* be monotonically sorted, otherwise + The {klass} *must* be monotonically sorted, otherwise wrong locations will likely be returned. Pandas does *not* check this for you. @@ -1421,7 +1421,7 @@ def factorize(self, sort=False, na_sentinel=-1): ---------- value : array_like Values to insert into `self`. - side : {'left', 'right'}, optional + side : {{'left', 'right'}}, optional If 'left', the index of the first suitable location found is given. If 'right', return the last such index. If there is no suitable index, return either 0 or N (where N is the length of `self`). @@ -1488,10 +1488,6 @@ def factorize(self, sort=False, na_sentinel=-1): >>> x.searchsorted(1) 0 # wrong result, correct would be 1 """ - - @Substitution(klass="Index") - @Appender(_shared_docs["searchsorted"]) - def searchsorted(self, value, side="left", sorter=None) -> np.ndarray: return algorithms.searchsorted(self._values, value, side=side, sorter=sorter) def drop_duplicates(self, keep="first", inplace=False): diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 1b3b6934aa53a..cb761062ef7b0 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -10,7 +10,7 @@ from pandas._libs.tslibs import timezones from pandas.compat.numpy import function as nv from pandas.errors import AbstractMethodError -from pandas.util._decorators import Appender, cache_readonly +from pandas.util._decorators import Appender, cache_readonly, doc from pandas.core.dtypes.common import ( ensure_int64, @@ -31,7 +31,7 @@ from pandas.core import algorithms from pandas.core.arrays import DatetimeArray, PeriodArray, TimedeltaArray from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin -from pandas.core.base import _shared_docs +from pandas.core.base import IndexOpsMixin import pandas.core.indexes.base as ibase from pandas.core.indexes.base import Index, _index_shared_docs from pandas.core.indexes.extension import ( @@ -206,7 +206,7 @@ def take(self, indices, axis=0, allow_fill=True, fill_value=None, **kwargs): self, indices, axis, allow_fill, fill_value, **kwargs ) - @Appender(_shared_docs["searchsorted"]) + @doc(IndexOpsMixin.searchsorted, klass="Datetime Index") def searchsorted(self, value, side="left", sorter=None): if isinstance(value, str): raise TypeError( diff --git a/pandas/core/series.py b/pandas/core/series.py index 256586f3d36a1..140b4c3cde6c5 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2470,8 +2470,7 @@ def __rmatmul__(self, other): """ return self.dot(np.transpose(other)) - @Substitution(klass="Series") - @Appender(base._shared_docs["searchsorted"]) + @doc(base.IndexOpsMixin.searchsorted, klass="Series") def searchsorted(self, value, side="left", sorter=None): return algorithms.searchsorted(self._values, value, side=side, sorter=sorter) From 9a4abae7c6fdf80f8e5d5860a9b4b3bf57326a95 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Thu, 13 Feb 2020 22:56:11 -0500 Subject: [PATCH 02/16] CLN: @doc - indexing.py --- pandas/core/indexing.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index cb8b9cc04fc24..634097fc5d4e1 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -5,7 +5,7 @@ from pandas._libs.indexing import _NDFrameIndexerBase from pandas._libs.lib import item_from_zerodim from pandas.errors import AbstractMethodError -from pandas.util._decorators import Appender +from pandas.util._decorators import doc from pandas.core.dtypes.common import ( is_float, @@ -90,7 +90,7 @@ class IndexingMixin: @property def iloc(self) -> "_iLocIndexer": - """ + f""" Purely integer-location based indexing for selection by position. ``.iloc[]`` is primarily integer position based (from ``0`` to @@ -123,9 +123,9 @@ def iloc(self) -> "_iLocIndexer": Examples -------- - >>> mydict = [{'a': 1, 'b': 2, 'c': 3, 'd': 4}, - ... {'a': 100, 'b': 200, 'c': 300, 'd': 400}, - ... {'a': 1000, 'b': 2000, 'c': 3000, 'd': 4000 }] + >>> mydict = [{{'a': 1, 'b': 2, 'c': 3, 'd': 4}}, + ... {{'a': 100, 'b': 200, 'c': 300, 'd': 400}}, + ... {{'a': 1000, 'b': 2000, 'c': 3000, 'd': 4000 }}] >>> df = pd.DataFrame(mydict) >>> df a b c d @@ -871,7 +871,7 @@ def _getbool_axis(self, key, axis: int): return self.obj._take_with_is_copy(inds, axis=axis) -@Appender(IndexingMixin.loc.__doc__) +@doc(IndexingMixin.loc) class _LocIndexer(_LocationIndexer): _takeable: bool = False _valid_types = ( @@ -883,7 +883,7 @@ class _LocIndexer(_LocationIndexer): # ------------------------------------------------------------------- # Key Checks - @Appender(_LocationIndexer._validate_key.__doc__) + @doc(_LocationIndexer._validate_key) def _validate_key(self, key, axis: int): # valid for a collection of labels (we check their presence later) @@ -1342,7 +1342,7 @@ def _validate_read_indexer( ) -@Appender(IndexingMixin.iloc.__doc__) +@doc(IndexingMixin.iloc) class _iLocIndexer(_LocationIndexer): _valid_types = ( "integer, integer slice (START point is INCLUDED, END " @@ -2078,7 +2078,7 @@ def __setitem__(self, key, value): self.obj._set_value(*key, value=value, takeable=self._takeable) -@Appender(IndexingMixin.at.__doc__) +@doc(IndexingMixin.at) class _AtIndexer(_ScalarAccessIndexer): _takeable = False @@ -2098,7 +2098,7 @@ def _convert_key(self, key, is_setter: bool = False): return tuple(lkey) -@Appender(IndexingMixin.iat.__doc__) +@doc(IndexingMixin.iat) class _iAtIndexer(_ScalarAccessIndexer): _takeable = True From 6cd80820e938a15dc5b1e76bb2f6bf079c47b0f4 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 15 Feb 2020 10:45:47 -0500 Subject: [PATCH 03/16] ENH: auto convert __doc__ to be format ready --- pandas/core/indexing.py | 8 ++++---- pandas/util/_decorators.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 634097fc5d4e1..359d762ac3ad7 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -90,7 +90,7 @@ class IndexingMixin: @property def iloc(self) -> "_iLocIndexer": - f""" + """ Purely integer-location based indexing for selection by position. ``.iloc[]`` is primarily integer position based (from ``0`` to @@ -123,9 +123,9 @@ def iloc(self) -> "_iLocIndexer": Examples -------- - >>> mydict = [{{'a': 1, 'b': 2, 'c': 3, 'd': 4}}, - ... {{'a': 100, 'b': 200, 'c': 300, 'd': 400}}, - ... {{'a': 1000, 'b': 2000, 'c': 3000, 'd': 4000 }}] + >>> mydict = [{'a': 1, 'b': 2, 'c': 3, 'd': 4}, + ... {'a': 100, 'b': 200, 'c': 300, 'd': 400}, + ... {'a': 1000, 'b': 2000, 'c': 3000, 'd': 4000 }] >>> df = pd.DataFrame(mydict) >>> df a b c d diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index d854be062fcbb..c529a297f2719 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -275,7 +275,8 @@ def wrapper(*args, **kwargs) -> Callable: elif hasattr(arg, "_docstr_template"): templates.append(arg._docstr_template) # type: ignore elif arg.__doc__: - templates.append(arg.__doc__) + doc_tmp = arg.__doc__.replace("{", "{{").replace("}", "}}") + templates.append(doc_tmp) wrapper._docstr_template = "".join(dedent(t) for t in templates) # type: ignore wrapper.__doc__ = wrapper._docstr_template.format(**kwargs) # type: ignore From 1534dad9643f6d3df1c7a9bbbf55420e3dff871f Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 15 Feb 2020 11:48:07 -0500 Subject: [PATCH 04/16] CLN: remove _shared_docs --- pandas/core/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/base.py b/pandas/core/base.py index 4656275c8da89..c1d28a87167d7 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -4,7 +4,7 @@ import builtins import textwrap -from typing import Dict, FrozenSet, List, Optional, Union +from typing import FrozenSet, List, Optional, Union import numpy as np @@ -36,7 +36,6 @@ from pandas.core.construction import create_series_with_explicit_dtype import pandas.core.nanops as nanops -_shared_docs: Dict[str, str] = dict() _indexops_doc_kwargs = dict( klass="IndexOpsMixin", inplace="", From 703f7a69f52b91f6a68f0324065ac62fafa32f02 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 15 Feb 2020 11:48:48 -0500 Subject: [PATCH 05/16] DOC: update klass in docstring --- pandas/core/indexes/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index cb761062ef7b0..a16293afe3c17 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -206,7 +206,7 @@ def take(self, indices, axis=0, allow_fill=True, fill_value=None, **kwargs): self, indices, axis, allow_fill, fill_value, **kwargs ) - @doc(IndexOpsMixin.searchsorted, klass="Datetime Index") + @doc(IndexOpsMixin.searchsorted, klass="Datetime-like Index") def searchsorted(self, value, side="left", sorter=None): if isinstance(value, str): raise TypeError( From 5077ab05aa69680e84625be73df0fe1f97a473b0 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Fri, 28 Feb 2020 20:20:26 -0500 Subject: [PATCH 06/16] update doc decorator to avoid formatting non-decorated function --- pandas/util/_decorators.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index c529a297f2719..dd84f25faa0dd 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -251,7 +251,7 @@ def doc(*args: Union[str, Callable], **kwargs: str) -> Callable[[F], F]: substitution on it. This decorator is robust even if func.__doc__ is None. This decorator will - add a variable "_docstr_template" to the wrapped function to save original + add a variable "_doc_args" to the wrapped function to save the original docstring template for potential usage. Parameters @@ -268,18 +268,18 @@ def decorator(func: F) -> F: def wrapper(*args, **kwargs) -> Callable: return func(*args, **kwargs) - templates = [func.__doc__ if func.__doc__ else ""] + wrapper._doc_args = [func.__doc__] if func.__doc__ else [] # type: ignore for arg in args: - if isinstance(arg, str): - templates.append(arg) - elif hasattr(arg, "_docstr_template"): - templates.append(arg._docstr_template) # type: ignore - elif arg.__doc__: - doc_tmp = arg.__doc__.replace("{", "{{").replace("}", "}}") - templates.append(doc_tmp) - - wrapper._docstr_template = "".join(dedent(t) for t in templates) # type: ignore - wrapper.__doc__ = wrapper._docstr_template.format(**kwargs) # type: ignore + if hasattr(arg, "_doc_args"): + wrapper._doc_args.extend(arg._doc_args) # type: ignore + elif isinstance(arg, str) or arg.__doc__: + wrapper._doc_args.append(arg) # type: ignore + + docs = [ + arg.format(**kwargs) if isinstance(arg, str) else arg.__doc__ + for arg in wrapper._doc_args # type: ignore + ] + wrapper.__doc__ = "".join([dedent(i) for i in docs]) return cast(F, wrapper) From 8ea6e1b9b16ede334cbe80ab461436bc383cd889 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 29 Feb 2020 18:09:59 -0500 Subject: [PATCH 07/16] fix dedent for __doc__ only --- pandas/util/_decorators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index dd84f25faa0dd..2556246966aa2 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -268,7 +268,7 @@ def decorator(func: F) -> F: def wrapper(*args, **kwargs) -> Callable: return func(*args, **kwargs) - wrapper._doc_args = [func.__doc__] if func.__doc__ else [] # type: ignore + wrapper._doc_args = [dedent(func.__doc__)] if func.__doc__ else [] # type: ignore for arg in args: if hasattr(arg, "_doc_args"): wrapper._doc_args.extend(arg._doc_args) # type: ignore @@ -276,10 +276,10 @@ def wrapper(*args, **kwargs) -> Callable: wrapper._doc_args.append(arg) # type: ignore docs = [ - arg.format(**kwargs) if isinstance(arg, str) else arg.__doc__ + arg.format(**kwargs) if isinstance(arg, str) else dedent(arg.__doc__) for arg in wrapper._doc_args # type: ignore ] - wrapper.__doc__ = "".join([dedent(i) for i in docs]) + wrapper.__doc__ = "".join(docs) return cast(F, wrapper) From 8e320ce3e55afd016f239b6a0306f8d78a9b56a5 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sun, 1 Mar 2020 14:02:25 -0500 Subject: [PATCH 08/16] organize the code in doc decorator --- pandas/util/_decorators.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 2556246966aa2..4e44f823c2458 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -250,9 +250,10 @@ def doc(*args: Union[str, Callable], **kwargs: str) -> Callable[[F], F]: A decorator take docstring templates, concatenate them and perform string substitution on it. - This decorator is robust even if func.__doc__ is None. This decorator will - add a variable "_doc_args" to the wrapped function to save the original - docstring template for potential usage. + This decorator will add a variable "_doc_args" to the wrapped function to + keep track the original docstring template for potential usage. If it should + be consider as a template, it will be saved as a string. Otherwise, it will + be saved as callable, and later user __doc__ and dedent to get docstring. Parameters ---------- @@ -268,18 +269,26 @@ def decorator(func: F) -> F: def wrapper(*args, **kwargs) -> Callable: return func(*args, **kwargs) - wrapper._doc_args = [dedent(func.__doc__)] if func.__doc__ else [] # type: ignore + # collecting and docstring templates + wrapper._doc_args: List[Union[str, Callable]] = [] # type: ignore + if func.__doc__: + wrapper._doc_args.append(dedent(func.__doc__)) # type: ignore + for arg in args: if hasattr(arg, "_doc_args"): wrapper._doc_args.extend(arg._doc_args) # type: ignore elif isinstance(arg, str) or arg.__doc__: wrapper._doc_args.append(arg) # type: ignore - docs = [ - arg.format(**kwargs) if isinstance(arg, str) else dedent(arg.__doc__) - for arg in wrapper._doc_args # type: ignore - ] - wrapper.__doc__ = "".join(docs) + # formatting templates and concatenating docstring + wrapper.__doc__ = "".join( + [ + arg.format(**kwargs) + if isinstance(arg, str) + else dedent(arg.__doc__) # type: ignore + for arg in wrapper._doc_args # type: ignore + ] + ) return cast(F, wrapper) From 1ee2cf7de056667e05f14633cdac7d28698bd4fb Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sun, 1 Mar 2020 14:39:08 -0500 Subject: [PATCH 09/16] fix test case for doc --- pandas/tests/util/test_doc.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pandas/tests/util/test_doc.py b/pandas/tests/util/test_doc.py index 7e5e24456b9a7..50859564e654f 100644 --- a/pandas/tests/util/test_doc.py +++ b/pandas/tests/util/test_doc.py @@ -14,13 +14,15 @@ def cumsum(whatever): @doc( cumsum, - """ - Examples - -------- + dedent( + """ + Examples + -------- - >>> cumavg([1, 2, 3]) - 2 - """, + >>> cumavg([1, 2, 3]) + 2 + """ + ), method="cumavg", operation="average", ) From 7bf242e03bc10aeee4a7c6df515df23fbca23859 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Tue, 3 Mar 2020 21:22:40 -0500 Subject: [PATCH 10/16] remove unused ignores --- pandas/util/_decorators.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 4e44f823c2458..018f080a7ac82 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -283,9 +283,7 @@ def wrapper(*args, **kwargs) -> Callable: # formatting templates and concatenating docstring wrapper.__doc__ = "".join( [ - arg.format(**kwargs) - if isinstance(arg, str) - else dedent(arg.__doc__) # type: ignore + arg.format(**kwargs) if isinstance(arg, str) else dedent(arg.__doc__) for arg in wrapper._doc_args # type: ignore ] ) From 61e87afc64bd69d1c3007d9592f00b51dbe4857a Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 7 Mar 2020 18:35:55 -0500 Subject: [PATCH 11/16] avoid type ignore in doc --- pandas/util/_decorators.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 018f080a7ac82..ced56704437bc 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -269,25 +269,28 @@ def decorator(func: F) -> F: def wrapper(*args, **kwargs) -> Callable: return func(*args, **kwargs) - # collecting and docstring templates - wrapper._doc_args: List[Union[str, Callable]] = [] # type: ignore + # collecting docstring and docstring templates + doc_args: List[Union[str, Callable]] = [] if func.__doc__: - wrapper._doc_args.append(dedent(func.__doc__)) # type: ignore + doc_args.append(dedent(func.__doc__)) for arg in args: if hasattr(arg, "_doc_args"): - wrapper._doc_args.extend(arg._doc_args) # type: ignore + doc_args.extend(arg._doc_args) # type: ignore elif isinstance(arg, str) or arg.__doc__: - wrapper._doc_args.append(arg) # type: ignore + doc_args.append(arg) # formatting templates and concatenating docstring wrapper.__doc__ = "".join( [ arg.format(**kwargs) if isinstance(arg, str) else dedent(arg.__doc__) - for arg in wrapper._doc_args # type: ignore + for arg in doc_args ] ) + # saving docstring components list + wrapper._doc_args = doc_args # type: ignore + return cast(F, wrapper) return decorator From 224cec82797bb370bc0a8df55f50b5a26a614fee Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 7 Mar 2020 18:36:37 -0500 Subject: [PATCH 12/16] avoid misleading var name --- pandas/util/_decorators.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index ced56704437bc..ffea590cd7ae8 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -250,10 +250,11 @@ def doc(*args: Union[str, Callable], **kwargs: str) -> Callable[[F], F]: A decorator take docstring templates, concatenate them and perform string substitution on it. - This decorator will add a variable "_doc_args" to the wrapped function to - keep track the original docstring template for potential usage. If it should - be consider as a template, it will be saved as a string. Otherwise, it will - be saved as callable, and later user __doc__ and dedent to get docstring. + This decorator will add a variable "_docstring_components" to the wrapped + function to keep track the original docstring template for potential usage. + If it should be consider as a template, it will be saved as a string. + Otherwise, it will be saved as callable, and later user __doc__ and dedent + to get docstring. Parameters ---------- @@ -270,26 +271,26 @@ def wrapper(*args, **kwargs) -> Callable: return func(*args, **kwargs) # collecting docstring and docstring templates - doc_args: List[Union[str, Callable]] = [] + docstring_components: List[Union[str, Callable]] = [] if func.__doc__: - doc_args.append(dedent(func.__doc__)) + docstring_components.append(dedent(func.__doc__)) for arg in args: - if hasattr(arg, "_doc_args"): - doc_args.extend(arg._doc_args) # type: ignore + if hasattr(arg, "_docstring_components"): + docstring_components.extend(arg._docstring_components) # type: ignore elif isinstance(arg, str) or arg.__doc__: - doc_args.append(arg) + docstring_components.append(arg) # formatting templates and concatenating docstring wrapper.__doc__ = "".join( [ arg.format(**kwargs) if isinstance(arg, str) else dedent(arg.__doc__) - for arg in doc_args + for arg in docstring_components ] ) # saving docstring components list - wrapper._doc_args = doc_args # type: ignore + wrapper._docstring_components = docstring_components # type: ignore return cast(F, wrapper) From e01e108ecc64622519675d85bd0f80e2d99c20c9 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 7 Mar 2020 19:09:14 -0500 Subject: [PATCH 13/16] remove trailing space --- pandas/util/_decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index ffea590cd7ae8..a3c58c8dc8ccc 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -250,7 +250,7 @@ def doc(*args: Union[str, Callable], **kwargs: str) -> Callable[[F], F]: A decorator take docstring templates, concatenate them and perform string substitution on it. - This decorator will add a variable "_docstring_components" to the wrapped + This decorator will add a variable "_docstring_components" to the wrapped function to keep track the original docstring template for potential usage. If it should be consider as a template, it will be saved as a string. Otherwise, it will be saved as callable, and later user __doc__ and dedent From 48cfcdafc220a3ab0806f67b60b0c9b083664ab8 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Sat, 7 Mar 2020 22:10:28 -0500 Subject: [PATCH 14/16] remove extra comment --- pandas/util/_decorators.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index a3c58c8dc8ccc..20c7101cfeb9e 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -289,7 +289,6 @@ def wrapper(*args, **kwargs) -> Callable: ] ) - # saving docstring components list wrapper._docstring_components = docstring_components # type: ignore return cast(F, wrapper) From 20f96f217831e2d6b5d223854e5b4d7310abe298 Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Mon, 9 Mar 2020 19:09:28 -0400 Subject: [PATCH 15/16] fix edge case for dedent --- pandas/util/_decorators.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 20c7101cfeb9e..7a804792174c7 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -284,7 +284,9 @@ def wrapper(*args, **kwargs) -> Callable: # formatting templates and concatenating docstring wrapper.__doc__ = "".join( [ - arg.format(**kwargs) if isinstance(arg, str) else dedent(arg.__doc__) + arg.format(**kwargs) + if isinstance(arg, str) + else dedent(arg.__doc__ or "") for arg in docstring_components ] ) From 76ed611e723b68caffd4d32e6805ff602a73a8cb Mon Sep 17 00:00:00 2001 From: HH-MWB Date: Tue, 10 Mar 2020 23:06:44 -0400 Subject: [PATCH 16/16] DOC: using _shared_docs for categorical.py --- pandas/core/arrays/categorical.py | 4 ++-- pandas/core/base.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 2e0d6f24e80fc..8e012e9ed0ea4 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -52,7 +52,7 @@ _extension_array_shared_docs, try_cast_to_ea, ) -from pandas.core.base import IndexOpsMixin, NoNewAttributesMixin, PandasObject +from pandas.core.base import NoNewAttributesMixin, PandasObject, _shared_docs import pandas.core.common as com from pandas.core.construction import array, extract_array, sanitize_array from pandas.core.indexers import check_array_indexer, deprecate_ndim_indexing @@ -1353,7 +1353,7 @@ def memory_usage(self, deep=False): """ return self._codes.nbytes + self.dtype.categories.memory_usage(deep=deep) - @doc(IndexOpsMixin.searchsorted, klass="Categorical") + @doc(_shared_docs["searchsorted"], klass="Categorical") def searchsorted(self, value, side="left", sorter=None): # searchsorted is very performance sensitive. By converting codes # to same dtype as self.codes, we get much faster performance. diff --git a/pandas/core/base.py b/pandas/core/base.py index c1d28a87167d7..a84e2371b0471 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -4,7 +4,7 @@ import builtins import textwrap -from typing import FrozenSet, List, Optional, Union +from typing import Dict, FrozenSet, List, Optional, Union import numpy as np @@ -36,6 +36,7 @@ from pandas.core.construction import create_series_with_explicit_dtype import pandas.core.nanops as nanops +_shared_docs: Dict[str, str] = dict() _indexops_doc_kwargs = dict( klass="IndexOpsMixin", inplace="", @@ -1401,9 +1402,9 @@ def memory_usage(self, deep=False): def factorize(self, sort=False, na_sentinel=-1): return algorithms.factorize(self, sort=sort, na_sentinel=na_sentinel) - @doc(klass="Index") - def searchsorted(self, value, side="left", sorter=None) -> np.ndarray: - """ + _shared_docs[ + "searchsorted" + ] = """ Find indices where elements should be inserted to maintain order. Find the indices into a sorted {klass} `self` such that, if the @@ -1487,6 +1488,9 @@ def searchsorted(self, value, side="left", sorter=None) -> np.ndarray: >>> x.searchsorted(1) 0 # wrong result, correct would be 1 """ + + @doc(_shared_docs["searchsorted"], klass="Index") + def searchsorted(self, value, side="left", sorter=None) -> np.ndarray: return algorithms.searchsorted(self._values, value, side=side, sorter=sorter) def drop_duplicates(self, keep="first", inplace=False):