Skip to content

API: Index.__cmp__(Series) return NotImplemented #61884

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 1 commit into from
Jul 16, 2025
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
1 change: 1 addition & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ Other API changes
- Index set operations (like union or intersection) will now ignore the dtype of
an empty ``RangeIndex`` or empty ``Index`` with object dtype when determining
the dtype of the resulting Index (:issue:`60797`)
- Comparison operations between :class:`Index` and :class:`Series` now consistently return :class:`Series` regardless of which object is on the left or right (:issue:`36759`)
- Numpy functions like ``np.isinf`` that return a bool dtype when called on a :class:`Index` object now return a bool-dtype :class:`Index` instead of ``np.ndarray`` (:issue:`52676`)

.. ---------------------------------------------------------------------------
Expand Down
16 changes: 5 additions & 11 deletions pandas/core/ops/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,14 @@ def _unpack_zerodim_and_defer(method: F, name: str) -> F:
-------
method
"""
stripped_name = name.removeprefix("__").removesuffix("__")
is_cmp = stripped_name in {"eq", "ne", "lt", "le", "gt", "ge"}

@wraps(method)
def new_method(self, other):
if is_cmp and isinstance(self, ABCIndex) and isinstance(other, ABCSeries):
# For comparison ops, Index does *not* defer to Series
pass
else:
prio = getattr(other, "__pandas_priority__", None)
if prio is not None:
if prio > self.__pandas_priority__:
# e.g. other is DataFrame while self is Index/Series/EA
return NotImplemented
prio = getattr(other, "__pandas_priority__", None)
if prio is not None:
if prio > self.__pandas_priority__:
# e.g. other is DataFrame while self is Index/Series/EA
return NotImplemented

other = item_from_zerodim(other)

Expand Down
9 changes: 2 additions & 7 deletions pandas/tests/arithmetic/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,19 @@ def xbox2(x):
return x.astype(bool)
return x

# rev_box: box to use for reversed comparisons
rev_box = xbox
if isinstance(right, Index) and isinstance(left, Series):
rev_box = np.array

result = xbox2(left == right)
expected = xbox(np.zeros(result.shape, dtype=np.bool_))

tm.assert_equal(result, expected)

result = xbox2(right == left)
tm.assert_equal(result, rev_box(expected))
tm.assert_equal(result, xbox(expected))

result = xbox2(left != right)
tm.assert_equal(result, ~expected)

result = xbox2(right != left)
tm.assert_equal(result, rev_box(~expected))
tm.assert_equal(result, xbox(~expected))

msg = "|".join(
[
Expand Down
11 changes: 9 additions & 2 deletions pandas/tests/arithmetic/test_datetime64.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,11 +770,18 @@ def test_dti_cmp_tdi_tzawareness(self, other):

result = dti == other
expected = np.array([False] * 10)
tm.assert_numpy_array_equal(result, expected)
if isinstance(other, Series):
tm.assert_series_equal(result, Series(expected, index=other.index))
else:
tm.assert_numpy_array_equal(result, expected)

result = dti != other
expected = np.array([True] * 10)
tm.assert_numpy_array_equal(result, expected)
if isinstance(other, Series):
tm.assert_series_equal(result, Series(expected, index=other.index))
else:
tm.assert_numpy_array_equal(result, expected)

msg = "Invalid comparison between"
with pytest.raises(TypeError, match=msg):
dti < other
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/indexes/multi/test_equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ def test_equals_op(idx):
with pytest.raises(ValueError, match="Lengths must match"):
index_a == series_b

tm.assert_numpy_array_equal(index_a == series_a, expected1)
tm.assert_numpy_array_equal(index_a == series_c, expected2)
tm.assert_series_equal(index_a == series_a, Series(expected1))
tm.assert_series_equal(index_a == series_c, Series(expected2))

# cases where length is 1 for one of them
with pytest.raises(ValueError, match="Lengths must match"):
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/indexes/test_old_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,8 +560,8 @@ def test_equals_op(self, simple_index):
with pytest.raises(ValueError, match=msg):
index_a == series_b

tm.assert_numpy_array_equal(index_a == series_a, expected1)
tm.assert_numpy_array_equal(index_a == series_c, expected2)
tm.assert_series_equal(index_a == series_a, Series(expected1))
tm.assert_series_equal(index_a == series_c, Series(expected2))

# cases where length is 1 for one of them
with pytest.raises(ValueError, match="Lengths must match"):
Expand Down
Loading