Skip to content

Commit 0f754b0

Browse files
committed
API: correctly provide __name__, __qualname__ for cum functions
closes pandas-dev#12021
1 parent fded942 commit 0f754b0

File tree

3 files changed

+54
-33
lines changed

3 files changed

+54
-33
lines changed

doc/source/whatsnew/v0.18.1.txt

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ API changes
103103
- ``CParserError`` is now a ``ValueError`` instead of just an ``Exception`` (:issue:`12551`)
104104

105105
- ``pd.show_versions()`` now includes ``pandas_datareader`` version (:issue:`12740`)
106+
- Provide a proper ``__name__`` and ``__qualname__`` attributes for generic functions (:issue:`12021`)
106107

107108
.. _whatsnew_0181.apply_resample:
108109

@@ -170,6 +171,7 @@ Performance Improvements
170171

171172

172173

174+
- Bug in ``__name__`` of ``.cum*`` functions (:issue:`12021`)
173175

174176

175177

pandas/core/generic.py

+39-32
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import pandas.core.missing as mis
2121
import pandas.core.datetools as datetools
2222
from pandas import compat
23-
from pandas.compat import map, zip, lrange, string_types, isidentifier
23+
from pandas.compat import map, zip, lrange, string_types, isidentifier, PY3
2424
from pandas.core.common import (isnull, notnull, is_list_like,
2525
_values_from_object, _maybe_promote,
2626
_maybe_box_datetimelike, ABCSeries,
@@ -4976,11 +4976,11 @@ def _add_numeric_operations(cls):
49764976
axis_descr, name, name2 = _doc_parms(cls)
49774977

49784978
cls.any = _make_logical_function(
4979-
'any', name, name2, axis_descr,
4979+
cls, 'any', name, name2, axis_descr,
49804980
'Return whether any element is True over requested axis',
49814981
nanops.nanany)
49824982
cls.all = _make_logical_function(
4983-
'all', name, name2, axis_descr,
4983+
cls, 'all', name, name2, axis_descr,
49844984
'Return whether all elements are True over requested axis',
49854985
nanops.nanall)
49864986

@@ -5008,18 +5008,18 @@ def mad(self, axis=None, skipna=None, level=None):
50085008
cls.mad = mad
50095009

50105010
cls.sem = _make_stat_function_ddof(
5011-
'sem', name, name2, axis_descr,
5011+
cls, 'sem', name, name2, axis_descr,
50125012
"Return unbiased standard error of the mean over requested "
50135013
"axis.\n\nNormalized by N-1 by default. This can be changed "
50145014
"using the ddof argument",
50155015
nanops.nansem)
50165016
cls.var = _make_stat_function_ddof(
5017-
'var', name, name2, axis_descr,
5017+
cls, 'var', name, name2, axis_descr,
50185018
"Return unbiased variance over requested axis.\n\nNormalized by "
50195019
"N-1 by default. This can be changed using the ddof argument",
50205020
nanops.nanvar)
50215021
cls.std = _make_stat_function_ddof(
5022-
'std', name, name2, axis_descr,
5022+
cls, 'std', name, name2, axis_descr,
50235023
"Return sample standard deviation over requested axis."
50245024
"\n\nNormalized by N-1 by default. This can be changed using the "
50255025
"ddof argument",
@@ -5038,54 +5038,54 @@ def compound(self, axis=None, skipna=None, level=None):
50385038
cls.compound = compound
50395039

50405040
cls.cummin = _make_cum_function(
5041-
'min', name, name2, axis_descr, "cumulative minimum",
5041+
cls, 'cummin', name, name2, axis_descr, "cumulative minimum",
50425042
lambda y, axis: np.minimum.accumulate(y, axis), np.inf, np.nan)
50435043
cls.cumsum = _make_cum_function(
5044-
'sum', name, name2, axis_descr, "cumulative sum",
5044+
cls, 'cumsum', name, name2, axis_descr, "cumulative sum",
50455045
lambda y, axis: y.cumsum(axis), 0., np.nan)
50465046
cls.cumprod = _make_cum_function(
5047-
'prod', name, name2, axis_descr, "cumulative product",
5047+
cls, 'cumprod', name, name2, axis_descr, "cumulative product",
50485048
lambda y, axis: y.cumprod(axis), 1., np.nan)
50495049
cls.cummax = _make_cum_function(
5050-
'max', name, name2, axis_descr, "cumulative max",
5050+
cls, 'cummax', name, name2, axis_descr, "cumulative max",
50515051
lambda y, axis: np.maximum.accumulate(y, axis), -np.inf, np.nan)
50525052

50535053
cls.sum = _make_stat_function(
5054-
'sum', name, name2, axis_descr,
5054+
cls, 'sum', name, name2, axis_descr,
50555055
'Return the sum of the values for the requested axis',
50565056
nanops.nansum)
50575057
cls.mean = _make_stat_function(
5058-
'mean', name, name2, axis_descr,
5058+
cls, 'mean', name, name2, axis_descr,
50595059
'Return the mean of the values for the requested axis',
50605060
nanops.nanmean)
50615061
cls.skew = _make_stat_function(
5062-
'skew', name, name2, axis_descr,
5062+
cls, 'skew', name, name2, axis_descr,
50635063
'Return unbiased skew over requested axis\nNormalized by N-1',
50645064
nanops.nanskew)
50655065
cls.kurt = _make_stat_function(
5066-
'kurt', name, name2, axis_descr,
5066+
cls, 'kurt', name, name2, axis_descr,
50675067
"Return unbiased kurtosis over requested axis using Fisher's "
50685068
"definition of\nkurtosis (kurtosis of normal == 0.0). Normalized "
50695069
"by N-1\n",
50705070
nanops.nankurt)
50715071
cls.kurtosis = cls.kurt
50725072
cls.prod = _make_stat_function(
5073-
'prod', name, name2, axis_descr,
5073+
cls, 'prod', name, name2, axis_descr,
50745074
'Return the product of the values for the requested axis',
50755075
nanops.nanprod)
50765076
cls.product = cls.prod
50775077
cls.median = _make_stat_function(
5078-
'median', name, name2, axis_descr,
5078+
cls, 'median', name, name2, axis_descr,
50795079
'Return the median of the values for the requested axis',
50805080
nanops.nanmedian)
50815081
cls.max = _make_stat_function(
5082-
'max', name, name2, axis_descr,
5082+
cls, 'max', name, name2, axis_descr,
50835083
"""This method returns the maximum of the values in the object.
50845084
If you want the *index* of the maximum, use ``idxmax``. This is
50855085
the equivalent of the ``numpy.ndarray`` method ``argmax``.""",
50865086
nanops.nanmax)
50875087
cls.min = _make_stat_function(
5088-
'min', name, name2, axis_descr,
5088+
cls, 'min', name, name2, axis_descr,
50895089
"""This method returns the minimum of the values in the object.
50905090
If you want the *index* of the minimum, use ``idxmin``. This is
50915091
the equivalent of the ``numpy.ndarray`` method ``argmin``.""",
@@ -5105,7 +5105,7 @@ def nanptp(values, axis=0, skipna=True):
51055105
return nmax - nmin
51065106

51075107
cls.ptp = _make_stat_function(
5108-
'ptp', name, name2, axis_descr,
5108+
cls, 'ptp', name, name2, axis_descr,
51095109
"""Returns the difference between the maximum value and the
51105110
minimum value in the object. This is the equivalent of the
51115111
``numpy.ndarray`` method ``ptp``.""",
@@ -5238,7 +5238,17 @@ def _doc_parms(cls):
52385238
%(outname)s : %(name1)s\n"""
52395239

52405240

5241-
def _make_stat_function(name, name1, name2, axis_descr, desc, f):
5241+
def _set_function_name(f, name, cls):
5242+
f.__name__ = name
5243+
if PY3:
5244+
f.__qualname__ = '{klass}.{name}'.format(
5245+
klass=cls.__name__,
5246+
name=name)
5247+
f.__module__ = cls.__module__
5248+
return f
5249+
5250+
5251+
def _make_stat_function(cls, name, name1, name2, axis_descr, desc, f):
52425252
@Substitution(outname=name, desc=desc, name1=name1, name2=name2,
52435253
axis_descr=axis_descr)
52445254
@Appender(_num_doc)
@@ -5255,11 +5265,10 @@ def stat_func(self, axis=None, skipna=None, level=None, numeric_only=None,
52555265
return self._reduce(f, name, axis=axis, skipna=skipna,
52565266
numeric_only=numeric_only)
52575267

5258-
stat_func.__name__ = name
5259-
return stat_func
5268+
return _set_function_name(stat_func, name, cls)
52605269

52615270

5262-
def _make_stat_function_ddof(name, name1, name2, axis_descr, desc, f):
5271+
def _make_stat_function_ddof(cls, name, name1, name2, axis_descr, desc, f):
52635272
@Substitution(outname=name, desc=desc, name1=name1, name2=name2,
52645273
axis_descr=axis_descr)
52655274
@Appender(_num_ddof_doc)
@@ -5276,17 +5285,16 @@ def stat_func(self, axis=None, skipna=None, level=None, ddof=1,
52765285
return self._reduce(f, name, axis=axis, numeric_only=numeric_only,
52775286
skipna=skipna, ddof=ddof)
52785287

5279-
stat_func.__name__ = name
5280-
return stat_func
5288+
return _set_function_name(stat_func, name, cls)
52815289

52825290

5283-
def _make_cum_function(name, name1, name2, axis_descr, desc, accum_func,
5291+
def _make_cum_function(cls, name, name1, name2, axis_descr, desc, accum_func,
52845292
mask_a, mask_b):
52855293
@Substitution(outname=name, desc=desc, name1=name1, name2=name2,
52865294
axis_descr=axis_descr)
52875295
@Appender("Return cumulative {0} over requested axis.".format(name) +
52885296
_cnum_doc)
5289-
def func(self, axis=None, dtype=None, out=None, skipna=True, **kwargs):
5297+
def cum_func(self, axis=None, dtype=None, out=None, skipna=True, **kwargs):
52905298
validate_kwargs(name, kwargs, 'out', 'dtype')
52915299
if axis is None:
52925300
axis = self._stat_axis_number
@@ -5312,11 +5320,10 @@ def func(self, axis=None, dtype=None, out=None, skipna=True, **kwargs):
53125320
d['copy'] = False
53135321
return self._constructor(result, **d).__finalize__(self)
53145322

5315-
func.__name__ = name
5316-
return func
5323+
return _set_function_name(cum_func, name, cls)
53175324

53185325

5319-
def _make_logical_function(name, name1, name2, axis_descr, desc, f):
5326+
def _make_logical_function(cls, name, name1, name2, axis_descr, desc, f):
53205327
@Substitution(outname=name, desc=desc, name1=name1, name2=name2,
53215328
axis_descr=axis_descr)
53225329
@Appender(_bool_doc)
@@ -5337,8 +5344,8 @@ def logical_func(self, axis=None, bool_only=None, skipna=None, level=None,
53375344
numeric_only=bool_only, filter_type='bool',
53385345
name=name)
53395346

5340-
logical_func.__name__ = name
5341-
return logical_func
5347+
return _set_function_name(logical_func, name, cls)
5348+
53425349

53435350
# install the indexes
53445351
for _name, _indexer in indexing.get_indexers_list():

pandas/tests/test_generic.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import pandas.core.common as com
1616
import pandas.lib as lib
1717

18-
from pandas.compat import range, zip
18+
from pandas.compat import range, zip, PY3
1919
from pandas import compat
2020
from pandas.util.testing import (assertRaisesRegexp,
2121
assert_series_equal,
@@ -549,6 +549,18 @@ def test_stat_unexpected_keyword(self):
549549
with assertRaisesRegexp(TypeError, 'unexpected keyword'):
550550
obj.any(epic=starwars) # logical_function
551551

552+
def test_api_compat(self):
553+
554+
# GH 12021
555+
# compat for __name__, __qualname__
556+
557+
obj = self._construct(5)
558+
for func in ['sum', 'cumsum', 'any', 'var']:
559+
f = getattr(obj, func)
560+
self.assertEqual(f.__name__, func)
561+
if PY3:
562+
self.assertTrue(f.__qualname__.endswith(func))
563+
552564

553565
class TestSeries(tm.TestCase, Generic):
554566
_typ = Series

0 commit comments

Comments
 (0)