diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index abe20ee0a91ce..e084f99ec5a2c 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -70,7 +70,6 @@ from pandas.core.indexes.frozen import FrozenList import pandas.core.missing as missing from pandas.core.ops import get_op_result_name, make_invalid_op -from pandas.core.ops.missing import dispatch_missing import pandas.core.sorting as sorting from pandas.core.strings import StringMethods @@ -144,27 +143,18 @@ def index_arithmetic_method(self, other): out = op(self, other) return Index(out, name=self.name) - other = self._validate_for_numeric_binop(other, op) - # handle time-based others if isinstance(other, (ABCDateOffset, np.timedelta64, timedelta)): return self._evaluate_with_timedelta_like(other, op) - elif isinstance(other, (datetime, np.datetime64)): - return self._evaluate_with_datetime_like(other, op) - values = self.values - with np.errstate(all="ignore"): - result = op(values, other) + other = self._validate_for_numeric_binop(other, op) - result = dispatch_missing(op, values, other, result) + from pandas import Series - attrs = self._get_attributes_dict() - attrs = self._maybe_update_attributes(attrs) - if op is divmod: - result = (Index(result[0], **attrs), Index(result[1], **attrs)) - else: - result = Index(result, **attrs) - return result + result = op(Series(self), other) + if isinstance(result, tuple): + return (Index(result[0]), Index(result[1])) + return Index(result) name = "__{name}__".format(name=op.__name__) # TODO: docstring? @@ -2361,10 +2351,14 @@ def _get_unique_index(self, dropna=False): def __add__(self, other): if isinstance(other, (ABCSeries, ABCDataFrame)): return NotImplemented - return Index(np.array(self) + other) + from pandas import Series + + return Index(Series(self) + other) def __radd__(self, other): - return Index(other + np.array(self)) + from pandas import Series + + return Index(other + Series(self)) def __iadd__(self, other): # alias for __add__ diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index ee5c670364485..43fe8f1a8698f 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -38,6 +38,7 @@ ) from pandas.core.dtypes.generic import ( ABCDataFrame, + ABCDatetimeArray, ABCIndex, ABCIndexClass, ABCSeries, @@ -1702,10 +1703,14 @@ def wrapper(left, right): # does inference in the case where `result` has object-dtype. return construct_result(left, result, index=left.index, name=res_name) + elif isinstance(right, (ABCDatetimeArray, pd.DatetimeIndex)): + result = op(left._values, right) + return construct_result(left, result, index=left.index, name=res_name) + lvalues = left.values rvalues = right - if isinstance(rvalues, ABCSeries): - rvalues = rvalues.values + if isinstance(rvalues, (ABCSeries, ABCIndexClass)): + rvalues = rvalues._values with np.errstate(all="ignore"): result = na_op(lvalues, rvalues) diff --git a/pandas/core/ops/missing.py b/pandas/core/ops/missing.py index 3698958261555..01bc345a40b83 100644 --- a/pandas/core/ops/missing.py +++ b/pandas/core/ops/missing.py @@ -148,40 +148,26 @@ def mask_zero_div_zero(x, y, result): return result -def dispatch_missing(op, left, right, result): +def dispatch_fill_zeros(op, left, right, result): """ - Fill nulls caused by division by zero, casting to a different dtype - if necessary. + Call fill_zeros with the appropriate fill value depending on the operation, + with special logic for divmod and rdivmod. Parameters ---------- op : function (operator.add, operator.div, ...) - left : object (Index for non-reversed ops) - right : object (Index fof reversed ops) + left : object (np.ndarray for non-reversed ops) + right : object (np.ndarray for reversed ops) result : ndarray Returns ------- - result : ndarray - """ - if op is operator.floordiv: - # Note: no need to do this for truediv; in py3 numpy behaves the way - # we want. - result = mask_zero_div_zero(left, right, result) - elif op is operator.mod: - result = fill_zeros(result, left, right, "__mod__", np.nan) - elif op is divmod: - res0 = mask_zero_div_zero(left, right, result[0]) - res1 = fill_zeros(result[1], left, right, "__divmod__", np.nan) - result = (res0, res1) - return result - + result : np.ndarray -# FIXME: de-duplicate with dispatch_missing -def dispatch_fill_zeros(op, left, right, result): - """ - Call fill_zeros with the appropriate fill value depending on the operation, - with special logic for divmod and rdivmod. + Notes + ----- + For divmod and rdivmod, the `result` parameter and returned `result` + is a 2-tuple of ndarray objects. """ if op is divmod: result = ( diff --git a/pandas/tests/arithmetic/test_object.py b/pandas/tests/arithmetic/test_object.py index f7f6ba8b114e7..fd9db80671360 100644 --- a/pandas/tests/arithmetic/test_object.py +++ b/pandas/tests/arithmetic/test_object.py @@ -103,18 +103,6 @@ def test_add_extension_scalar(self, other, box, op): result = op(arr, other) tm.assert_equal(result, expected) - @pytest.mark.parametrize( - "box", - [ - pytest.param( - pd.Index, - marks=pytest.mark.xfail(reason="Does not mask nulls", raises=TypeError), - ), - pd.Series, - pd.DataFrame, - ], - ids=lambda x: x.__name__, - ) def test_objarr_add_str(self, box): ser = pd.Series(["x", np.nan, "x"]) expected = pd.Series(["xa", np.nan, "xa"]) @@ -125,18 +113,6 @@ def test_objarr_add_str(self, box): result = ser + "a" tm.assert_equal(result, expected) - @pytest.mark.parametrize( - "box", - [ - pytest.param( - pd.Index, - marks=pytest.mark.xfail(reason="Does not mask nulls", raises=TypeError), - ), - pd.Series, - pd.DataFrame, - ], - ids=lambda x: x.__name__, - ) def test_objarr_radd_str(self, box): ser = pd.Series(["x", np.nan, "x"]) expected = pd.Series(["ax", np.nan, "ax"]) diff --git a/pandas/tests/indexes/test_common.py b/pandas/tests/indexes/test_common.py index 605df9971a567..0e9aa07a4c05a 100644 --- a/pandas/tests/indexes/test_common.py +++ b/pandas/tests/indexes/test_common.py @@ -144,6 +144,7 @@ def test_set_name_methods(self, indices): assert res is None assert indices.name == new_name assert indices.names == [new_name] + # FIXME: dont leave commented-out # with pytest.raises(TypeError, match="list-like"): # # should still fail even if it would be the right length # ind.set_names("a")