Skip to content

REF: share DatetimeLikeIndex methods higher up #42178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 0 additions & 6 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
30 changes: 28 additions & 2 deletions pandas/core/indexes/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions pandas/tests/indexes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
5 changes: 3 additions & 2 deletions pandas/tests/indexes/datetimes/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
offsets,
)
import pandas._testing as tm
from pandas.core.arrays import DatetimeArray


class TestTimeSeries:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions pandas/tests/scalar/test_nat.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
@pytest.mark.parametrize(
"nat,idx",
[
(Timestamp("NaT"), DatetimeIndex),
(Timestamp("NaT"), DatetimeArray),
(Timedelta("NaT"), TimedeltaIndex),
(Period("NaT", freq="M"), PeriodArray),
],
Expand Down Expand Up @@ -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":
Expand All @@ -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))
Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/series/accessors/test_dt_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
)
import pandas._testing as tm
from pandas.core.arrays import (
DatetimeArray,
PeriodArray,
TimedeltaArray,
)
Expand All @@ -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",
Expand Down