From e4ae503a77589e8374a1eaa82d7b0c3f03b78649 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 21 Jun 2021 12:36:04 -0700 Subject: [PATCH 1/2] REF: share DatetimeLikeIndex methods higher up --- pandas/core/indexes/datetimelike.py | 19 ------------- pandas/core/indexes/datetimes.py | 6 ---- pandas/core/indexes/extension.py | 28 ++++++++++++++++++- pandas/tests/indexes/common.py | 5 ++-- pandas/tests/indexes/datetimes/test_misc.py | 5 ++-- pandas/tests/scalar/test_nat.py | 6 ++-- .../series/accessors/test_dt_accessor.py | 3 +- 7 files changed, 38 insertions(+), 34 deletions(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 53ab2e45edede..5e4194a0a3aee 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -62,7 +62,6 @@ from pandas.core.indexes.extension import ( NDArrayBackedExtensionIndex, inherit_names, - make_wrapped_arith_op, ) from pandas.core.tools.timedeltas import to_timedelta @@ -98,7 +97,6 @@ class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex): hasnans = cache_readonly( DatetimeLikeArrayMixin._hasnans.fget # type: ignore[attr-defined] ) - _hasnans = hasnans # for index / array -agnostic code @property def _is_all_dates(self) -> bool: @@ -450,23 +448,6 @@ def _partial_date_slice( # -------------------------------------------------------------------- # Arithmetic Methods - __add__ = make_wrapped_arith_op("__add__") - __sub__ = make_wrapped_arith_op("__sub__") - __radd__ = make_wrapped_arith_op("__radd__") - __rsub__ = make_wrapped_arith_op("__rsub__") - __pow__ = make_wrapped_arith_op("__pow__") - __rpow__ = make_wrapped_arith_op("__rpow__") - __mul__ = make_wrapped_arith_op("__mul__") - __rmul__ = make_wrapped_arith_op("__rmul__") - __floordiv__ = make_wrapped_arith_op("__floordiv__") - __rfloordiv__ = make_wrapped_arith_op("__rfloordiv__") - __mod__ = make_wrapped_arith_op("__mod__") - __rmod__ = make_wrapped_arith_op("__rmod__") - __divmod__ = make_wrapped_arith_op("__divmod__") - __rdivmod__ = make_wrapped_arith_op("__rdivmod__") - __truediv__ = make_wrapped_arith_op("__truediv__") - __rtruediv__ = make_wrapped_arith_op("__rtruediv__") - def shift(self: _T, periods: int = 1, freq=None) -> _T: """ Shift index by desired number of time frequency increments. diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index d9eaaa763fde8..837f545b8a1da 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -116,16 +116,10 @@ def _new_DatetimeIndex(cls, d): @inherit_names(["is_normalized", "_resolution_obj"], DatetimeArray, cache=True) @inherit_names( [ - "_bool_ops", - "_object_ops", - "_field_ops", - "_datetimelike_ops", - "_datetimelike_methods", "tz", "tzinfo", "dtype", "to_pydatetime", - "_has_same_tz", "_format_native_types", "date", "time", diff --git a/pandas/core/indexes/extension.py b/pandas/core/indexes/extension.py index 6ff20f7d009bc..58a7b904553f8 100644 --- a/pandas/core/indexes/extension.py +++ b/pandas/core/indexes/extension.py @@ -170,7 +170,16 @@ def method(self, other): # a chance to implement ops before we unwrap them. # See https://github.com/pandas-dev/pandas/issues/31109 return NotImplemented - meth = getattr(self._data, opname) + + try: + meth = getattr(self._data, opname) + except AttributeError as err: + # e.g. Categorical, IntervalArray + cls = type(self).__name__ + raise TypeError( + f"cannot perform {opname} with this index type: {cls}" + ) from err + result = meth(_maybe_unwrap_index(other)) return _wrap_arithmetic_op(self, other, result) @@ -267,6 +276,23 @@ def _simple_new( __le__ = _make_wrapped_comparison_op("__le__") __ge__ = _make_wrapped_comparison_op("__ge__") + __add__ = make_wrapped_arith_op("__add__") + __sub__ = make_wrapped_arith_op("__sub__") + __radd__ = make_wrapped_arith_op("__radd__") + __rsub__ = make_wrapped_arith_op("__rsub__") + __pow__ = make_wrapped_arith_op("__pow__") + __rpow__ = make_wrapped_arith_op("__rpow__") + __mul__ = make_wrapped_arith_op("__mul__") + __rmul__ = make_wrapped_arith_op("__rmul__") + __floordiv__ = make_wrapped_arith_op("__floordiv__") + __rfloordiv__ = make_wrapped_arith_op("__rfloordiv__") + __mod__ = make_wrapped_arith_op("__mod__") + __rmod__ = make_wrapped_arith_op("__rmod__") + __divmod__ = make_wrapped_arith_op("__divmod__") + __rdivmod__ = make_wrapped_arith_op("__rdivmod__") + __truediv__ = make_wrapped_arith_op("__truediv__") + __rtruediv__ = make_wrapped_arith_op("__rtruediv__") + @property def _has_complex_internals(self) -> bool: # used to avoid libreduction code paths, which raise or require conversion diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index 54050e4a49b84..e02b2559bb8ae 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -166,11 +166,12 @@ def test_numeric_compat(self, simple_index): return typ = type(idx._data).__name__ + cls = type(idx).__name__ lmsg = "|".join( [ rf"unsupported operand type\(s\) for \*: '{typ}' and 'int'", "cannot perform (__mul__|__truediv__|__floordiv__) with " - f"this index type: {typ}", + f"this index type: ({cls}|{typ})", ] ) with pytest.raises(TypeError, match=lmsg): @@ -179,7 +180,7 @@ def test_numeric_compat(self, simple_index): [ rf"unsupported operand type\(s\) for \*: 'int' and '{typ}'", "cannot perform (__rmul__|__rtruediv__|__rfloordiv__) with " - f"this index type: {typ}", + f"this index type: ({cls}|{typ})", ] ) with pytest.raises(TypeError, match=rmsg): diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index fe84699a89bc5..408ed2db316ca 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -16,6 +16,7 @@ offsets, ) import pandas._testing as tm +from pandas.core.arrays import DatetimeArray class TestTimeSeries: @@ -223,7 +224,7 @@ def test_datetimeindex_accessors(self): dti.name = "name" # non boolean accessors -> return Index - for accessor in DatetimeIndex._field_ops: + for accessor in DatetimeArray._field_ops: if accessor in ["week", "weekofyear"]: # GH#33595 Deprecate week and weekofyear continue @@ -233,7 +234,7 @@ def test_datetimeindex_accessors(self): assert res.name == "name" # boolean accessors -> return array - for accessor in DatetimeIndex._bool_ops: + for accessor in DatetimeArray._bool_ops: res = getattr(dti, accessor) assert len(res) == 365 assert isinstance(res, np.ndarray) diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index 08c5ea706111a..1f935a5debde1 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -38,7 +38,7 @@ @pytest.mark.parametrize( "nat,idx", [ - (Timestamp("NaT"), DatetimeIndex), + (Timestamp("NaT"), DatetimeArray), (Timedelta("NaT"), TimedeltaIndex), (Period("NaT", freq="M"), PeriodArray), ], @@ -84,7 +84,7 @@ def test_nat_vector_field_access(): ser = Series(idx) - for field in DatetimeIndex._field_ops: + for field in DatetimeArray._field_ops: # weekday is a property of DTI, but a method # on NaT/Timestamp for compat with datetime if field == "weekday": @@ -97,7 +97,7 @@ def test_nat_vector_field_access(): expected = [getattr(x, field) for x in idx] tm.assert_series_equal(result, Series(expected)) - for field in DatetimeIndex._bool_ops: + for field in DatetimeArray._bool_ops: result = getattr(ser.dt, field) expected = [getattr(x, field) for x in idx] tm.assert_series_equal(result, Series(expected)) diff --git a/pandas/tests/series/accessors/test_dt_accessor.py b/pandas/tests/series/accessors/test_dt_accessor.py index 076de881eaf96..61a22dad5d09b 100644 --- a/pandas/tests/series/accessors/test_dt_accessor.py +++ b/pandas/tests/series/accessors/test_dt_accessor.py @@ -33,6 +33,7 @@ ) import pandas._testing as tm from pandas.core.arrays import ( + DatetimeArray, PeriodArray, TimedeltaArray, ) @@ -47,7 +48,7 @@ def test_dt_namespace_accessor(self): ok_for_period = PeriodArray._datetimelike_ops ok_for_period_methods = ["strftime", "to_timestamp", "asfreq"] - ok_for_dt = DatetimeIndex._datetimelike_ops + ok_for_dt = DatetimeArray._datetimelike_ops ok_for_dt_methods = [ "to_period", "to_pydatetime", From e321aaf04326866d15a5cd72becdbd8e23958eb3 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 21 Jun 2021 12:36:52 -0700 Subject: [PATCH 2/2] privatize --- pandas/core/indexes/extension.py | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pandas/core/indexes/extension.py b/pandas/core/indexes/extension.py index 58a7b904553f8..857d36c18de1f 100644 --- a/pandas/core/indexes/extension.py +++ b/pandas/core/indexes/extension.py @@ -159,7 +159,7 @@ def wrapper(self, other): return wrapper -def make_wrapped_arith_op(opname: str): +def _make_wrapped_arith_op(opname: str): def method(self, other): if ( isinstance(other, Index) @@ -276,22 +276,22 @@ def _simple_new( __le__ = _make_wrapped_comparison_op("__le__") __ge__ = _make_wrapped_comparison_op("__ge__") - __add__ = make_wrapped_arith_op("__add__") - __sub__ = make_wrapped_arith_op("__sub__") - __radd__ = make_wrapped_arith_op("__radd__") - __rsub__ = make_wrapped_arith_op("__rsub__") - __pow__ = make_wrapped_arith_op("__pow__") - __rpow__ = make_wrapped_arith_op("__rpow__") - __mul__ = make_wrapped_arith_op("__mul__") - __rmul__ = make_wrapped_arith_op("__rmul__") - __floordiv__ = make_wrapped_arith_op("__floordiv__") - __rfloordiv__ = make_wrapped_arith_op("__rfloordiv__") - __mod__ = make_wrapped_arith_op("__mod__") - __rmod__ = make_wrapped_arith_op("__rmod__") - __divmod__ = make_wrapped_arith_op("__divmod__") - __rdivmod__ = make_wrapped_arith_op("__rdivmod__") - __truediv__ = make_wrapped_arith_op("__truediv__") - __rtruediv__ = make_wrapped_arith_op("__rtruediv__") + __add__ = _make_wrapped_arith_op("__add__") + __sub__ = _make_wrapped_arith_op("__sub__") + __radd__ = _make_wrapped_arith_op("__radd__") + __rsub__ = _make_wrapped_arith_op("__rsub__") + __pow__ = _make_wrapped_arith_op("__pow__") + __rpow__ = _make_wrapped_arith_op("__rpow__") + __mul__ = _make_wrapped_arith_op("__mul__") + __rmul__ = _make_wrapped_arith_op("__rmul__") + __floordiv__ = _make_wrapped_arith_op("__floordiv__") + __rfloordiv__ = _make_wrapped_arith_op("__rfloordiv__") + __mod__ = _make_wrapped_arith_op("__mod__") + __rmod__ = _make_wrapped_arith_op("__rmod__") + __divmod__ = _make_wrapped_arith_op("__divmod__") + __rdivmod__ = _make_wrapped_arith_op("__rdivmod__") + __truediv__ = _make_wrapped_arith_op("__truediv__") + __rtruediv__ = _make_wrapped_arith_op("__rtruediv__") @property def _has_complex_internals(self) -> bool: