diff --git a/pandas/_typing.py b/pandas/_typing.py index a2bb168c1e2da..0044b269eb7b5 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -9,7 +9,7 @@ from pandas.core.dtypes.dtypes import ExtensionDtype from pandas.core.dtypes.generic import ( - ABCExtensionArray, ABCIndexClass, ABCSeries, ABCSparseSeries) + ABCDataFrame, ABCExtensionArray, ABCIndexClass, ABCSeries, ABCSparseSeries) AnyArrayLike = TypeVar('AnyArrayLike', ABCExtensionArray, @@ -22,3 +22,5 @@ Timedelta) Dtype = Union[str, np.dtype, ExtensionDtype] FilePathOrBuffer = Union[str, Path, IO[AnyStr]] + +FrameOrSeries = TypeVar('FrameOrSeries', ABCSeries, ABCDataFrame) diff --git a/pandas/core/groupby/base.py b/pandas/core/groupby/base.py index 823a4155bc2b8..cffe0e589c6bc 100644 --- a/pandas/core/groupby/base.py +++ b/pandas/core/groupby/base.py @@ -3,11 +3,6 @@ hold the whitelist of methods that are exposed on the SeriesGroupBy and the DataFrameGroupBy objects. """ - -import types - -from pandas.util._decorators import make_signature - from pandas.core.dtypes.common import is_list_like, is_scalar @@ -91,67 +86,3 @@ def _gotitem(self, key, ndim, subset=None): cython_cast_blacklist = frozenset(['rank', 'count', 'size', 'idxmin', 'idxmax']) - - -def whitelist_method_generator(base, klass, whitelist): - """ - Yields all GroupBy member defs for DataFrame/Series names in whitelist. - - Parameters - ---------- - base : class - base class - klass : class - class where members are defined. - Should be Series or DataFrame - whitelist : list - list of names of klass methods to be constructed - - Returns - ------- - The generator yields a sequence of strings, each suitable for exec'ing, - that define implementations of the named methods for DataFrameGroupBy - or SeriesGroupBy. - - Since we don't want to override methods explicitly defined in the - base class, any such name is skipped. - """ - - method_wrapper_template = \ - """def %(name)s(%(sig)s) : - \""" - %(doc)s - \""" - f = %(self)s.__getattr__('%(name)s') - return f(%(args)s)""" - property_wrapper_template = \ - """@property -def %(name)s(self) : - \"""%(doc)s\""" - return self.__getattr__('%(name)s')""" - - for name in whitelist: - # don't override anything that was explicitly defined - # in the base class - if hasattr(base, name): - continue - # ugly, but we need the name string itself in the method. - f = getattr(klass, name) - doc = f.__doc__ - doc = doc if type(doc) == str else '' - if isinstance(f, types.MethodType): - wrapper_template = method_wrapper_template - decl, args = make_signature(f) - # pass args by name to f because otherwise - # GroupBy._make_wrapper won't know whether - # we passed in an axis parameter. - args_by_name = ['{0}={0}'.format(arg) for arg in args[1:]] - params = {'name': name, - 'doc': doc, - 'sig': ','.join(decl), - 'self': args[0], - 'args': ','.join(args_by_name)} - else: - wrapper_template = property_wrapper_template - params = {'name': name, 'doc': doc} - yield wrapper_template % params diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py index 57d14cb4c15d7..35ffa552913ae 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -11,7 +11,7 @@ from functools import partial from textwrap import dedent import typing -from typing import Any, Callable, List, Union +from typing import Any, Callable, FrozenSet, Iterator, List, Type, Union import warnings import numpy as np @@ -27,6 +27,7 @@ is_integer_dtype, is_interval_dtype, is_numeric_dtype, is_scalar) from pandas.core.dtypes.missing import isna, notna +from pandas._typing import FrameOrSeries import pandas.core.algorithms as algorithms from pandas.core.base import DataError, SpecificationError import pandas.core.common as com @@ -48,6 +49,51 @@ AggScalar = Union[str, Callable[..., Any]] +def whitelist_method_generator(base_class: Type[GroupBy], + klass: Type[FrameOrSeries], + whitelist: FrozenSet[str], + ) -> Iterator[str]: + """ + Yields all GroupBy member defs for DataFrame/Series names in whitelist. + + Parameters + ---------- + base_class : Groupby class + base class + klass : DataFrame or Series class + class where members are defined. + whitelist : frozenset + Set of names of klass methods to be constructed + + Returns + ------- + The generator yields a sequence of strings, each suitable for exec'ing, + that define implementations of the named methods for DataFrameGroupBy + or SeriesGroupBy. + + Since we don't want to override methods explicitly defined in the + base class, any such name is skipped. + """ + property_wrapper_template = \ + """@property +def %(name)s(self) : + \"""%(doc)s\""" + return self.__getattr__('%(name)s')""" + + for name in whitelist: + # don't override anything that was explicitly defined + # in the base class + if hasattr(base_class, name): + continue + # ugly, but we need the name string itself in the method. + f = getattr(klass, name) + doc = f.__doc__ + doc = doc if type(doc) == str else '' + wrapper_template = property_wrapper_template + params = {'name': name, 'doc': doc} + yield wrapper_template % params + + class NDFrameGroupBy(GroupBy): def _iterate_slices(self): @@ -685,7 +731,7 @@ class SeriesGroupBy(GroupBy): # Make class defs of attributes on SeriesGroupBy whitelist _apply_whitelist = base.series_apply_whitelist - for _def_str in base.whitelist_method_generator( + for _def_str in whitelist_method_generator( GroupBy, Series, _apply_whitelist): exec(_def_str) @@ -1289,7 +1335,7 @@ class DataFrameGroupBy(NDFrameGroupBy): # # Make class defs of attributes on DataFrameGroupBy whitelist. - for _def_str in base.whitelist_method_generator( + for _def_str in whitelist_method_generator( GroupBy, DataFrame, _apply_whitelist): exec(_def_str) diff --git a/pandas/tests/util/test_util.py b/pandas/tests/util/test_util.py index a3b82ecc12a1b..88ce48245dc70 100644 --- a/pandas/tests/util/test_util.py +++ b/pandas/tests/util/test_util.py @@ -5,8 +5,6 @@ import pandas.compat as compat from pandas.compat import raise_with_traceback -from pandas.util._decorators import deprecate_kwarg, make_signature -from pandas.util._validators import validate_kwargs import pandas.util.testing as tm @@ -37,22 +35,6 @@ def test_numpy_err_state_is_default(): assert np.geterr() == expected -@pytest.mark.parametrize("func,expected", [ - # Case where the func does not have default kwargs. - (validate_kwargs, (["fname", "kwargs", "compat_args"], - ["fname", "kwargs", "compat_args"])), - - # Case where the func does have default kwargs. - (deprecate_kwarg, (["old_arg_name", "new_arg_name", - "mapping=None", "stacklevel=2"], - ["old_arg_name", "new_arg_name", - "mapping", "stacklevel"])) -]) -def test_make_signature(func, expected): - # see gh-17608 - assert make_signature(func) == expected - - def test_raise_with_traceback(): with pytest.raises(LookupError, match="error_text"): try: diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index ac23fa5d7b0ad..cdda02324ba06 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -319,33 +319,3 @@ def indent(text, indents=1): return '' jointext = ''.join(['\n'] + [' '] * indents) return jointext.join(text.split('\n')) - - -def make_signature(func): - """ - Returns a tuple containing the paramenter list with defaults - and parameter list. - - Examples - -------- - >>> def f(a, b, c=2): - >>> return a * b * c - >>> print(make_signature(f)) - (['a', 'b', 'c=2'], ['a', 'b', 'c']) - """ - - spec = inspect.getfullargspec(func) - if spec.defaults is None: - n_wo_defaults = len(spec.args) - defaults = ('',) * n_wo_defaults - else: - n_wo_defaults = len(spec.args) - len(spec.defaults) - defaults = ('',) * n_wo_defaults + tuple(spec.defaults) - args = [] - for var, default in zip(spec.args, defaults): - args.append(var if default == '' else var + '=' + repr(default)) - if spec.varargs: - args.append('*' + spec.varargs) - if spec.varkw: - args.append('**' + spec.varkw) - return args, spec.args