Skip to content

COMPAT: Return NotImplemented for subclassing #31136

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 10 commits into from
Jan 28, 2020
24 changes: 20 additions & 4 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -1299,8 +1299,16 @@ def __add__(self, other):
# TimedeltaIndex, ndarray[timedelta64]
result = self._add_delta(other)
elif is_object_dtype(other):
# e.g. Array/Index of DateOffset objects
result = self._addsub_object_array(other, operator.add)
from pandas import Index

if type(other) is Index:
# e.g. Array/Index of DateOffset objects
# We deliberately check for other being exactly an Index, rather
# than a subclass, to let Index subclasses take control of binary
# operations. See https://github.com/pandas-dev/pandas/issues/31109
result = self._addsub_object_array(other, operator.add)
else:
return NotImplemented
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other):
# DatetimeIndex, ndarray[datetime64]
return self._add_datetime_arraylike(other)
Expand Down Expand Up @@ -1354,8 +1362,16 @@ def __sub__(self, other):
# TimedeltaIndex, ndarray[timedelta64]
result = self._add_delta(-other)
elif is_object_dtype(other):
# e.g. Array/Index of DateOffset objects
result = self._addsub_object_array(other, operator.sub)
from pandas import Index

if type(other) is Index:
# e.g. Array/Index of DateOffset objects
# We deliberately check for other being exactly an Index, rather
# than a subclass, to let Index subclasses take control of binary
# operations. See https://github.com/pandas-dev/pandas/issues/31109
result = self._addsub_object_array(other, operator.sub)
else:
return NotImplemented
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other):
# DatetimeIndex, ndarray[datetime64]
result = self._sub_datetime_arraylike(other)
Expand Down
33 changes: 33 additions & 0 deletions pandas/tests/arithmetic/test_object.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Arithmetic tests for DataFrame/Series/Index/Array classes that should
# behave identically.
# Specifically for object dtype
import datetime
from decimal import Decimal
import operator

Expand Down Expand Up @@ -323,3 +324,35 @@ def test_rsub_object(self):

with pytest.raises(TypeError):
np.array([True, pd.Timestamp.now()]) - index

def test_index_ops_defer_to_unknown_subclasses(self):
# https://github.com/pandas-dev/pandas/issues/31109
class MyIndex(pd.Index):

_calls: int

@classmethod
def _simple_new(cls, values, name=None, dtype=None):
result = object.__new__(cls)
result._data = values
result._index_data = values
result._name = name
result._calls = 0

return result._reset_identity()

def __add__(self, other):
self._calls += 1
return self._simple_new(self._index_data + other.to_list())

def __radd__(self, other):
return self.__add__(other)

values = np.array(
[datetime.date(2000, 1, 1), datetime.date(2000, 1, 2)], dtype=object
)
a = MyIndex._simple_new(values)
b = pd.timedelta_range("1D", periods=2, freq="D")
result = b + a
assert isinstance(result, MyIndex)
assert a._calls == 1