diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index f3c01efed6d43..ffa38cbc3d658 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -462,7 +462,8 @@ def wrapper(left, right): res_name = get_op_result_name(left, right) lvalues = extract_array(left, extract_numpy=True) - result = arithmetic_op(lvalues, right, op, str_rep) + rvalues = extract_array(right, extract_numpy=True) + result = arithmetic_op(lvalues, rvalues, op, str_rep) return _construct_result(left, result, index=left.index, name=res_name) diff --git a/pandas/core/ops/array_ops.py b/pandas/core/ops/array_ops.py index 414e241af7bbd..de34258f863d0 100644 --- a/pandas/core/ops/array_ops.py +++ b/pandas/core/ops/array_ops.py @@ -24,17 +24,14 @@ ) from pandas.core.dtypes.generic import ( ABCDatetimeArray, - ABCDatetimeIndex, ABCExtensionArray, ABCIndex, ABCIndexClass, ABCSeries, ABCTimedeltaArray, - ABCTimedeltaIndex, ) from pandas.core.dtypes.missing import isna, notna -from pandas.core.construction import extract_array from pandas.core.ops import missing from pandas.core.ops.dispatch import dispatch_to_extension_op, should_extension_dispatch from pandas.core.ops.invalid import invalid_comparison @@ -178,22 +175,10 @@ def arithmetic_op( from pandas.core.ops import maybe_upcast_for_op - keep_null_freq = isinstance( - right, - ( - ABCDatetimeIndex, - ABCDatetimeArray, - ABCTimedeltaIndex, - ABCTimedeltaArray, - Timestamp, - ), - ) - - # NB: We assume that extract_array has already been called on `left`, but - # cannot make the same assumption about `right`. This is because we need - # to define `keep_null_freq` before calling extract_array on it. + # NB: We assume that extract_array has already been called + # on `left` and `right`. lvalues = left - rvalues = extract_array(right, extract_numpy=True) + rvalues = right rvalues = maybe_upcast_for_op(rvalues, lvalues.shape) @@ -203,7 +188,7 @@ def arithmetic_op( # TimedeltaArray, DatetimeArray, and Timestamp are included here # because they have `freq` attribute which is handled correctly # by dispatch_to_extension_op. - res_values = dispatch_to_extension_op(op, lvalues, rvalues, keep_null_freq) + res_values = dispatch_to_extension_op(op, lvalues, rvalues) else: with np.errstate(all="ignore"): diff --git a/pandas/core/ops/dispatch.py b/pandas/core/ops/dispatch.py index 016a89eb56da3..6a2aba4264874 100644 --- a/pandas/core/ops/dispatch.py +++ b/pandas/core/ops/dispatch.py @@ -5,8 +5,6 @@ import numpy as np -from pandas.errors import NullFrequencyError - from pandas.core.dtypes.common import ( is_datetime64_dtype, is_extension_array_dtype, @@ -97,10 +95,7 @@ def should_series_dispatch(left, right, op): def dispatch_to_extension_op( - op, - left: Union[ABCExtensionArray, np.ndarray], - right: Any, - keep_null_freq: bool = False, + op, left: Union[ABCExtensionArray, np.ndarray], right: Any, ): """ Assume that left or right is a Series backed by an ExtensionArray, @@ -111,9 +106,6 @@ def dispatch_to_extension_op( op : binary operator left : ExtensionArray or np.ndarray right : object - keep_null_freq : bool, default False - Whether to re-raise a NullFrequencyError unchanged, as opposed to - catching and raising TypeError. Returns ------- @@ -131,20 +123,7 @@ def dispatch_to_extension_op( # The op calls will raise TypeError if the op is not defined # on the ExtensionArray - - try: - res_values = op(left, right) - except NullFrequencyError: - # DatetimeIndex and TimedeltaIndex with freq == None raise ValueError - # on add/sub of integers (or int-like). We re-raise as a TypeError. - if keep_null_freq: - # TODO: remove keep_null_freq after Timestamp+int deprecation - # GH#22535 is enforced - raise - raise TypeError( - "incompatible type for a datetime/timedelta " - "operation [{name}]".format(name=op.__name__) - ) + res_values = op(left, right) return res_values diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index 90a41d43a2a88..b77c9a2bddcfa 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1844,6 +1844,7 @@ def test_dt64_mul_div_numeric_invalid(self, one, dt64_series): with pytest.raises(TypeError, match=msg): one / dt64_series + # TODO: parametrize over box @pytest.mark.parametrize("op", ["__add__", "__radd__", "__sub__", "__rsub__"]) @pytest.mark.parametrize("tz", [None, "Asia/Tokyo"]) def test_dt64_series_add_intlike(self, tz, op): diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 8ea38170bb489..4a37a56f5029c 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -1046,6 +1046,58 @@ def test_td64arr_add_sub_numeric_arr_invalid(self, box_with_array, vec, dtype): with pytest.raises(TypeError): vector - tdser + # TODO: parameterize over box and de-duplicate + def test_tdi_add_sub_int(self, one): + # Variants of `one` for #19012, deprecated GH#22535 + rng = timedelta_range("1 days 09:00:00", freq="H", periods=10) + msg = "Addition/subtraction of integers" + + with pytest.raises(TypeError, match=msg): + rng + one + with pytest.raises(TypeError, match=msg): + rng += one + with pytest.raises(TypeError, match=msg): + rng - one + with pytest.raises(TypeError, match=msg): + rng -= one + + # TODO: parameterize over box and de-duplicate + @pytest.mark.parametrize("box", [np.array, pd.Index]) + def test_tdi_add_sub_integer_array(self, box): + # GH#19959, deprecated GH#22535 + rng = timedelta_range("1 days 09:00:00", freq="H", periods=3) + other = box([4, 3, 2]) + msg = "Addition/subtraction of integers and integer-arrays" + + with pytest.raises(TypeError, match=msg): + rng + other + + with pytest.raises(TypeError, match=msg): + other + rng + + with pytest.raises(TypeError, match=msg): + rng - other + + with pytest.raises(TypeError, match=msg): + other - rng + + # TODO: parameterize over box and de-duplicate + @pytest.mark.parametrize("box", [np.array, pd.Index]) + def test_tdi_addsub_integer_array_no_freq(self, box): + # GH#19959 + tdi = TimedeltaIndex(["1 Day", "NaT", "3 Hours"]) + other = box([14, -1, 16]) + msg = "Addition/subtraction of integers" + + with pytest.raises(TypeError, match=msg): + tdi + other + with pytest.raises(TypeError, match=msg): + other + tdi + with pytest.raises(TypeError, match=msg): + tdi - other + with pytest.raises(TypeError, match=msg): + other - tdi + # ------------------------------------------------------------------ # Operations with timedelta-like others diff --git a/pandas/tests/indexes/datetimes/test_arithmetic.py b/pandas/tests/indexes/datetimes/test_shift.py similarity index 99% rename from pandas/tests/indexes/datetimes/test_arithmetic.py rename to pandas/tests/indexes/datetimes/test_shift.py index 6dd7bee8207d3..6f8315debdfa9 100644 --- a/pandas/tests/indexes/datetimes/test_arithmetic.py +++ b/pandas/tests/indexes/datetimes/test_shift.py @@ -10,7 +10,7 @@ import pandas.util.testing as tm -class TestDatetimeIndexArithmetic: +class TestDatetimeIndexShift: # ------------------------------------------------------------- # DatetimeIndex.shift is used in integer addition diff --git a/pandas/tests/indexes/period/test_arithmetic.py b/pandas/tests/indexes/period/test_shift.py similarity index 99% rename from pandas/tests/indexes/period/test_arithmetic.py rename to pandas/tests/indexes/period/test_shift.py index f8274a82f1b6f..7543f85c6d138 100644 --- a/pandas/tests/indexes/period/test_arithmetic.py +++ b/pandas/tests/indexes/period/test_shift.py @@ -6,7 +6,7 @@ import pandas.util.testing as tm -class TestPeriodIndexArithmetic: +class TestPeriodIndexShift: # --------------------------------------------------------------- # PeriodIndex.shift is used by __add__ and __sub__ diff --git a/pandas/tests/indexes/timedeltas/test_arithmetic.py b/pandas/tests/indexes/timedeltas/test_arithmetic.py index 3603719eab036..680593b93eebc 100644 --- a/pandas/tests/indexes/timedeltas/test_arithmetic.py +++ b/pandas/tests/indexes/timedeltas/test_arithmetic.py @@ -97,61 +97,6 @@ def test_shift_no_freq(self): with pytest.raises(NullFrequencyError): tdi.shift(2) - # ------------------------------------------------------------- - # Binary operations TimedeltaIndex and integer - - def test_tdi_add_sub_int(self, one): - # Variants of `one` for #19012, deprecated GH#22535 - rng = timedelta_range("1 days 09:00:00", freq="H", periods=10) - msg = "Addition/subtraction of integers" - - with pytest.raises(TypeError, match=msg): - rng + one - with pytest.raises(TypeError, match=msg): - rng += one - with pytest.raises(TypeError, match=msg): - rng - one - with pytest.raises(TypeError, match=msg): - rng -= one - - # ------------------------------------------------------------- - # __add__/__sub__ with integer arrays - - @pytest.mark.parametrize("box", [np.array, pd.Index]) - def test_tdi_add_sub_integer_array(self, box): - # GH#19959, deprecated GH#22535 - rng = timedelta_range("1 days 09:00:00", freq="H", periods=3) - other = box([4, 3, 2]) - msg = "Addition/subtraction of integers and integer-arrays" - - with pytest.raises(TypeError, match=msg): - rng + other - - with pytest.raises(TypeError, match=msg): - other + rng - - with pytest.raises(TypeError, match=msg): - rng - other - - with pytest.raises(TypeError, match=msg): - other - rng - - @pytest.mark.parametrize("box", [np.array, pd.Index]) - def test_tdi_addsub_integer_array_no_freq(self, box): - # GH#19959 - tdi = TimedeltaIndex(["1 Day", "NaT", "3 Hours"]) - other = box([14, -1, 16]) - msg = "Addition/subtraction of integers" - - with pytest.raises(TypeError, match=msg): - tdi + other - with pytest.raises(TypeError, match=msg): - other + tdi - with pytest.raises(TypeError, match=msg): - tdi - other - with pytest.raises(TypeError, match=msg): - other - tdi - # ------------------------------------------------------------- # Binary operations TimedeltaIndex and timedelta-like # Note: add and sub are tested in tests.test_arithmetic, in-place