|
11 | 11 | from functools import partial
|
12 | 12 | from textwrap import dedent
|
13 | 13 | import typing
|
14 |
| -from typing import Any, Callable, FrozenSet, Iterator, Sequence, Type, Union |
| 14 | +from typing import Any, Callable, FrozenSet, Sequence, Type, Union |
15 | 15 | import warnings
|
16 | 16 |
|
17 | 17 | import numpy as np
|
|
70 | 70 | ScalarResult = typing.TypeVar("ScalarResult")
|
71 | 71 |
|
72 | 72 |
|
73 |
| -def whitelist_method_generator( |
74 |
| - base_class: Type[GroupBy], klass: Type[FrameOrSeries], whitelist: FrozenSet[str] |
75 |
| -) -> Iterator[str]: |
| 73 | +def generate_property(name: str, klass: Type[FrameOrSeries]): |
76 | 74 | """
|
77 |
| - Yields all GroupBy member defs for DataFrame/Series names in whitelist. |
| 75 | + Create a property for a GroupBy subclass to dispatch to DataFrame/Series. |
| 76 | +
|
| 77 | + Parameters |
| 78 | + ---------- |
| 79 | + name : str |
| 80 | + klass : {DataFrame, Series} |
| 81 | +
|
| 82 | + Returns |
| 83 | + ------- |
| 84 | + property |
| 85 | + """ |
| 86 | + |
| 87 | + def prop(self): |
| 88 | + return self._make_wrapper(name) |
| 89 | + |
| 90 | + parent_method = getattr(klass, name) |
| 91 | + prop.__doc__ = parent_method.__doc__ or "" |
| 92 | + prop.__name__ = name |
| 93 | + return property(prop) |
| 94 | + |
| 95 | + |
| 96 | +def pin_whitelisted_properties(klass: Type[FrameOrSeries], whitelist: FrozenSet[str]): |
| 97 | + """ |
| 98 | + Create GroupBy member defs for DataFrame/Series names in a whitelist. |
78 | 99 |
|
79 | 100 | Parameters
|
80 | 101 | ----------
|
81 |
| - base_class : Groupby class |
82 |
| - base class |
83 | 102 | klass : DataFrame or Series class
|
84 | 103 | class where members are defined.
|
85 |
| - whitelist : frozenset |
| 104 | + whitelist : frozenset[str] |
86 | 105 | Set of names of klass methods to be constructed
|
87 | 106 |
|
88 | 107 | Returns
|
89 | 108 | -------
|
90 |
| - The generator yields a sequence of strings, each suitable for exec'ing, |
91 |
| - that define implementations of the named methods for DataFrameGroupBy |
92 |
| - or SeriesGroupBy. |
| 109 | + class decorator |
93 | 110 |
|
| 111 | + Notes |
| 112 | + ----- |
94 | 113 | Since we don't want to override methods explicitly defined in the
|
95 | 114 | base class, any such name is skipped.
|
96 | 115 | """
|
97 |
| - property_wrapper_template = """@property |
98 |
| -def %(name)s(self) : |
99 |
| - \"""%(doc)s\""" |
100 |
| - return self.__getattr__('%(name)s')""" |
101 |
| - |
102 |
| - for name in whitelist: |
103 |
| - # don't override anything that was explicitly defined |
104 |
| - # in the base class |
105 |
| - if hasattr(base_class, name): |
106 |
| - continue |
107 |
| - # ugly, but we need the name string itself in the method. |
108 |
| - f = getattr(klass, name) |
109 |
| - doc = f.__doc__ |
110 |
| - doc = doc if type(doc) == str else "" |
111 |
| - wrapper_template = property_wrapper_template |
112 |
| - params = {"name": name, "doc": doc} |
113 |
| - yield wrapper_template % params |
| 116 | + |
| 117 | + def pinner(cls): |
| 118 | + for name in whitelist: |
| 119 | + if hasattr(cls, name): |
| 120 | + # don't override anything that was explicitly defined |
| 121 | + # in the base class |
| 122 | + continue |
| 123 | + |
| 124 | + prop = generate_property(name, klass) |
| 125 | + setattr(cls, name, prop) |
| 126 | + |
| 127 | + return cls |
| 128 | + |
| 129 | + return pinner |
114 | 130 |
|
115 | 131 |
|
116 | 132 | class NDFrameGroupBy(GroupBy):
|
@@ -747,13 +763,9 @@ def filter(self, func, dropna=True, *args, **kwargs):
|
747 | 763 | return self._apply_filter(indices, dropna)
|
748 | 764 |
|
749 | 765 |
|
| 766 | +@pin_whitelisted_properties(Series, base.series_apply_whitelist) |
750 | 767 | class SeriesGroupBy(GroupBy):
|
751 |
| - # |
752 |
| - # Make class defs of attributes on SeriesGroupBy whitelist |
753 |
| - |
754 | 768 | _apply_whitelist = base.series_apply_whitelist
|
755 |
| - for _def_str in whitelist_method_generator(GroupBy, Series, _apply_whitelist): |
756 |
| - exec(_def_str) |
757 | 769 |
|
758 | 770 | @property
|
759 | 771 | def _selection_name(self):
|
@@ -1368,15 +1380,11 @@ def pct_change(self, periods=1, fill_method="pad", limit=None, freq=None):
|
1368 | 1380 | return (filled / shifted) - 1
|
1369 | 1381 |
|
1370 | 1382 |
|
| 1383 | +@pin_whitelisted_properties(DataFrame, base.dataframe_apply_whitelist) |
1371 | 1384 | class DataFrameGroupBy(NDFrameGroupBy):
|
1372 | 1385 |
|
1373 | 1386 | _apply_whitelist = base.dataframe_apply_whitelist
|
1374 | 1387 |
|
1375 |
| - # |
1376 |
| - # Make class defs of attributes on DataFrameGroupBy whitelist. |
1377 |
| - for _def_str in whitelist_method_generator(GroupBy, DataFrame, _apply_whitelist): |
1378 |
| - exec(_def_str) |
1379 |
| - |
1380 | 1388 | _block_agg_axis = 1
|
1381 | 1389 |
|
1382 | 1390 | _agg_see_also_doc = dedent(
|
|
0 commit comments