Skip to content

Commit da57db2

Browse files
authored
REF: share DatetimeLikeIndex methods higher up (#42178)
1 parent 781f82d commit da57db2

File tree

7 files changed

+39
-35
lines changed

7 files changed

+39
-35
lines changed

pandas/core/indexes/datetimelike.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
from pandas.core.indexes.extension import (
6464
NDArrayBackedExtensionIndex,
6565
inherit_names,
66-
make_wrapped_arith_op,
6766
)
6867
from pandas.core.tools.timedeltas import to_timedelta
6968

@@ -99,7 +98,6 @@ class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex):
9998
hasnans = cache_readonly(
10099
DatetimeLikeArrayMixin._hasnans.fget # type: ignore[attr-defined]
101100
)
102-
_hasnans = hasnans # for index / array -agnostic code
103101

104102
@property
105103
def _is_all_dates(self) -> bool:
@@ -451,23 +449,6 @@ def _partial_date_slice(
451449
# --------------------------------------------------------------------
452450
# Arithmetic Methods
453451

454-
__add__ = make_wrapped_arith_op("__add__")
455-
__sub__ = make_wrapped_arith_op("__sub__")
456-
__radd__ = make_wrapped_arith_op("__radd__")
457-
__rsub__ = make_wrapped_arith_op("__rsub__")
458-
__pow__ = make_wrapped_arith_op("__pow__")
459-
__rpow__ = make_wrapped_arith_op("__rpow__")
460-
__mul__ = make_wrapped_arith_op("__mul__")
461-
__rmul__ = make_wrapped_arith_op("__rmul__")
462-
__floordiv__ = make_wrapped_arith_op("__floordiv__")
463-
__rfloordiv__ = make_wrapped_arith_op("__rfloordiv__")
464-
__mod__ = make_wrapped_arith_op("__mod__")
465-
__rmod__ = make_wrapped_arith_op("__rmod__")
466-
__divmod__ = make_wrapped_arith_op("__divmod__")
467-
__rdivmod__ = make_wrapped_arith_op("__rdivmod__")
468-
__truediv__ = make_wrapped_arith_op("__truediv__")
469-
__rtruediv__ = make_wrapped_arith_op("__rtruediv__")
470-
471452
def shift(self: _T, periods: int = 1, freq=None) -> _T:
472453
"""
473454
Shift index by desired number of time frequency increments.

pandas/core/indexes/datetimes.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,10 @@ def _new_DatetimeIndex(cls, d):
116116
@inherit_names(["is_normalized", "_resolution_obj"], DatetimeArray, cache=True)
117117
@inherit_names(
118118
[
119-
"_bool_ops",
120-
"_object_ops",
121-
"_field_ops",
122-
"_datetimelike_ops",
123-
"_datetimelike_methods",
124119
"tz",
125120
"tzinfo",
126121
"dtype",
127122
"to_pydatetime",
128-
"_has_same_tz",
129123
"_format_native_types",
130124
"date",
131125
"time",

pandas/core/indexes/extension.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def wrapper(self, other):
159159
return wrapper
160160

161161

162-
def make_wrapped_arith_op(opname: str):
162+
def _make_wrapped_arith_op(opname: str):
163163
def method(self, other):
164164
if (
165165
isinstance(other, Index)
@@ -170,7 +170,16 @@ def method(self, other):
170170
# a chance to implement ops before we unwrap them.
171171
# See https://github.com/pandas-dev/pandas/issues/31109
172172
return NotImplemented
173-
meth = getattr(self._data, opname)
173+
174+
try:
175+
meth = getattr(self._data, opname)
176+
except AttributeError as err:
177+
# e.g. Categorical, IntervalArray
178+
cls = type(self).__name__
179+
raise TypeError(
180+
f"cannot perform {opname} with this index type: {cls}"
181+
) from err
182+
174183
result = meth(_maybe_unwrap_index(other))
175184
return _wrap_arithmetic_op(self, other, result)
176185

@@ -267,6 +276,23 @@ def _simple_new(
267276
__le__ = _make_wrapped_comparison_op("__le__")
268277
__ge__ = _make_wrapped_comparison_op("__ge__")
269278

279+
__add__ = _make_wrapped_arith_op("__add__")
280+
__sub__ = _make_wrapped_arith_op("__sub__")
281+
__radd__ = _make_wrapped_arith_op("__radd__")
282+
__rsub__ = _make_wrapped_arith_op("__rsub__")
283+
__pow__ = _make_wrapped_arith_op("__pow__")
284+
__rpow__ = _make_wrapped_arith_op("__rpow__")
285+
__mul__ = _make_wrapped_arith_op("__mul__")
286+
__rmul__ = _make_wrapped_arith_op("__rmul__")
287+
__floordiv__ = _make_wrapped_arith_op("__floordiv__")
288+
__rfloordiv__ = _make_wrapped_arith_op("__rfloordiv__")
289+
__mod__ = _make_wrapped_arith_op("__mod__")
290+
__rmod__ = _make_wrapped_arith_op("__rmod__")
291+
__divmod__ = _make_wrapped_arith_op("__divmod__")
292+
__rdivmod__ = _make_wrapped_arith_op("__rdivmod__")
293+
__truediv__ = _make_wrapped_arith_op("__truediv__")
294+
__rtruediv__ = _make_wrapped_arith_op("__rtruediv__")
295+
270296
@property
271297
def _has_complex_internals(self) -> bool:
272298
# used to avoid libreduction code paths, which raise or require conversion

pandas/tests/indexes/common.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,12 @@ def test_numeric_compat(self, simple_index):
166166
return
167167

168168
typ = type(idx._data).__name__
169+
cls = type(idx).__name__
169170
lmsg = "|".join(
170171
[
171172
rf"unsupported operand type\(s\) for \*: '{typ}' and 'int'",
172173
"cannot perform (__mul__|__truediv__|__floordiv__) with "
173-
f"this index type: {typ}",
174+
f"this index type: ({cls}|{typ})",
174175
]
175176
)
176177
with pytest.raises(TypeError, match=lmsg):
@@ -179,7 +180,7 @@ def test_numeric_compat(self, simple_index):
179180
[
180181
rf"unsupported operand type\(s\) for \*: 'int' and '{typ}'",
181182
"cannot perform (__rmul__|__rtruediv__|__rfloordiv__) with "
182-
f"this index type: {typ}",
183+
f"this index type: ({cls}|{typ})",
183184
]
184185
)
185186
with pytest.raises(TypeError, match=rmsg):

pandas/tests/indexes/datetimes/test_misc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
offsets,
1717
)
1818
import pandas._testing as tm
19+
from pandas.core.arrays import DatetimeArray
1920

2021

2122
class TestTimeSeries:
@@ -223,7 +224,7 @@ def test_datetimeindex_accessors(self):
223224
dti.name = "name"
224225

225226
# non boolean accessors -> return Index
226-
for accessor in DatetimeIndex._field_ops:
227+
for accessor in DatetimeArray._field_ops:
227228
if accessor in ["week", "weekofyear"]:
228229
# GH#33595 Deprecate week and weekofyear
229230
continue
@@ -233,7 +234,7 @@ def test_datetimeindex_accessors(self):
233234
assert res.name == "name"
234235

235236
# boolean accessors -> return array
236-
for accessor in DatetimeIndex._bool_ops:
237+
for accessor in DatetimeArray._bool_ops:
237238
res = getattr(dti, accessor)
238239
assert len(res) == 365
239240
assert isinstance(res, np.ndarray)

pandas/tests/scalar/test_nat.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
@pytest.mark.parametrize(
3939
"nat,idx",
4040
[
41-
(Timestamp("NaT"), DatetimeIndex),
41+
(Timestamp("NaT"), DatetimeArray),
4242
(Timedelta("NaT"), TimedeltaIndex),
4343
(Period("NaT", freq="M"), PeriodArray),
4444
],
@@ -84,7 +84,7 @@ def test_nat_vector_field_access():
8484

8585
ser = Series(idx)
8686

87-
for field in DatetimeIndex._field_ops:
87+
for field in DatetimeArray._field_ops:
8888
# weekday is a property of DTI, but a method
8989
# on NaT/Timestamp for compat with datetime
9090
if field == "weekday":
@@ -97,7 +97,7 @@ def test_nat_vector_field_access():
9797
expected = [getattr(x, field) for x in idx]
9898
tm.assert_series_equal(result, Series(expected))
9999

100-
for field in DatetimeIndex._bool_ops:
100+
for field in DatetimeArray._bool_ops:
101101
result = getattr(ser.dt, field)
102102
expected = [getattr(x, field) for x in idx]
103103
tm.assert_series_equal(result, Series(expected))

pandas/tests/series/accessors/test_dt_accessor.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
)
3434
import pandas._testing as tm
3535
from pandas.core.arrays import (
36+
DatetimeArray,
3637
PeriodArray,
3738
TimedeltaArray,
3839
)
@@ -47,7 +48,7 @@ def test_dt_namespace_accessor(self):
4748

4849
ok_for_period = PeriodArray._datetimelike_ops
4950
ok_for_period_methods = ["strftime", "to_timestamp", "asfreq"]
50-
ok_for_dt = DatetimeIndex._datetimelike_ops
51+
ok_for_dt = DatetimeArray._datetimelike_ops
5152
ok_for_dt_methods = [
5253
"to_period",
5354
"to_pydatetime",

0 commit comments

Comments
 (0)