diff --git a/pandas/core/arrays/sparse.py b/pandas/core/arrays/sparse.py index 6114e578dc90f..0bea62cc08939 100644 --- a/pandas/core/arrays/sparse.py +++ b/pandas/core/arrays/sparse.py @@ -1706,12 +1706,11 @@ def sparse_arithmetic_method(self, other): @classmethod def _create_comparison_method(cls, op): - def cmp_method(self, other): - op_name = op.__name__ - - if op_name in {'and_', 'or_'}: - op_name = op_name[:-1] + op_name = op.__name__ + if op_name in {'and_', 'or_'}: + op_name = op_name[:-1] + def cmp_method(self, other): if isinstance(other, (ABCSeries, ABCIndexClass)): # Rely on pandas to unbox and dispatch to us. return NotImplemented @@ -1740,7 +1739,7 @@ def cmp_method(self, other): fill_value=fill_value, dtype=np.bool_) - name = '__{name}__'.format(name=op.__name__) + name = '__{name}__'.format(name=op_name) return compat.set_function_name(cmp_method, name, cls) @classmethod diff --git a/pandas/core/indexes/frozen.py b/pandas/core/indexes/frozen.py index 982645ebd5124..4ae6008f61f4e 100644 --- a/pandas/core/indexes/frozen.py +++ b/pandas/core/indexes/frozen.py @@ -21,8 +21,17 @@ from pandas.io.formats.printing import pprint_thing -class FrozenList(PandasObject, list): +def _make_disabled(name): + def _disabled(self, *args, **kwargs): + """This method will not function because object is immutable.""" + raise TypeError("'{cls}' does not support mutable operations." + .format(cls=self.__class__.__name__)) + _disabled.__name__ = name + return _disabled + + +class FrozenList(PandasObject, list): """ Container that doesn't allow setting item *but* because it's technically non-hashable, will be used @@ -103,11 +112,6 @@ def __reduce__(self): def __hash__(self): return hash(tuple(self)) - def _disabled(self, *args, **kwargs): - """This method will not function because object is immutable.""" - raise TypeError("'%s' does not support mutable operations." % - self.__class__.__name__) - def __unicode__(self): return pprint_thing(self, quote_strings=True, escape_chars=('\t', '\r', '\n')) @@ -116,8 +120,16 @@ def __repr__(self): return "%s(%s)" % (self.__class__.__name__, str(self)) - __setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled - pop = append = extend = remove = sort = insert = _disabled + __setitem__ = _make_disabled("__setitem__") + __setslice__ = _make_disabled("__setslice__") + __delitem__ = _make_disabled("__delitem__") + __delslice__ = _make_disabled("__delslice__") + pop = _make_disabled("pop") + append = _make_disabled("append") + extend = _make_disabled("extend") + remove = _make_disabled("remove") + sort = _make_disabled("sort") + insert = _make_disabled("insert") class FrozenNDArray(PandasObject, np.ndarray): @@ -133,13 +145,13 @@ def __new__(cls, data, dtype=None, copy=False): res = np.array(data, dtype=dtype, copy=copy).view(cls) return res - def _disabled(self, *args, **kwargs): - """This method will not function because object is immutable.""" - raise TypeError("'%s' does not support mutable operations." % - self.__class__) - - __setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled - put = itemset = fill = _disabled + __setitem__ = _make_disabled("__setitem__") + __setslice__ = _make_disabled("__setslice__") + __delitem__ = _make_disabled("__delitem__") + __delslice__ = _make_disabled("__delslice__") + put = _make_disabled("put") + itemset = _make_disabled("itemset") + fill = _make_disabled("fill") def _shallow_copy(self): return self.view() diff --git a/pandas/tests/test_base.py b/pandas/tests/test_base.py index 657f5f193c85e..ca3391303b626 100644 --- a/pandas/tests/test_base.py +++ b/pandas/tests/test_base.py @@ -2,6 +2,7 @@ from __future__ import print_function from datetime import datetime, timedelta +import inspect import re import sys @@ -1345,3 +1346,87 @@ def test_to_numpy_dtype(as_series): expected = np.array(['2000-01-01T05', '2001-01-01T05'], dtype='M8[ns]') tm.assert_numpy_array_equal(result, expected) + + +def check_pinned_names(cls): + """ + Check that the any dynamically-defined methods have the correct + names, i.e. not 'wrapper'. + """ + special_cases = { + "isnull": "isna", + "notnull": "notna", + "iteritems": "items", + "__bool__": "__nonzero__", + "__div__": "__truediv__", + "__rdiv__": "__rtruediv__", + "__rmul__": "__mul__", + "__req__": "__eq__", + + "T": "transpose", + "_unpickle_compat": "__setstate__", + + # Questionable + "get_level_values": "_get_level_values", + "_xs": "xs", + + # _Window + "agg": "aggregate", + + # Categorical + "take": "take_nd", + "to_list": "tolist", + + # Timestamp + "daysinmonth": "days_in_month", + "astimezone": "tz_convert", + "weekofyear": "week", + } + # special cases that we are more restrictive about, possibly fixing + # them at some point. + class_special_cases = { + "SparseArray": { + "get_values": "to_dense", + }, + "FrozenList": { + "__add__": "union", + "__iadd__": "union", + "__imul__": "__mul__", + }, + } + ignore = { + "_create_comparison_method", + } + if 'Subclassed' in cls.__name__: + # dummy classes defined in tests + return + for name in dir(cls): + try: + # e.g. properties may not be accessible on the class + attr = getattr(cls, name) + except Exception: + continue + if name in ignore: + continue + if inspect.ismethod(attr) or inspect.isfunction(attr): + # isfunction check is needed in py3 + expected = special_cases.get(name, name) + result = attr.__name__ + + cls_kludge = class_special_cases.get(cls.__name__, {}) + expected = cls_kludge.get(name, expected) + + assert result in [name, expected], (result, expected, name, + cls.__name__) + + +@pytest.mark.parametrize('klass', + sorted(PandasObject.__subclasses__(), + key=lambda x: x.__name__) + [ + pd.Timestamp, + pd.Period, + pd.Timedelta, + pd.Interval + ]) +def test_pinned_names(klass): + check_pinned_names(klass)