diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index c932d793038c2..376ca4b3f7dd3 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -223,6 +223,7 @@ Removal of prior version deprecations/changes - All arguments except ``name`` in :meth:`Index.rename` are now keyword only (:issue:`56493`) - All arguments except the first ``path``-like argument in IO writers are now keyword only (:issue:`54229`) - Changed behavior of :meth:`Series.__getitem__` and :meth:`Series.__setitem__` to always treat integer keys as labels, never as positional, consistent with :class:`DataFrame` behavior (:issue:`50617`) +- Disallow a callable argument to :meth:`Series.iloc` to return a ``tuple`` (:issue:`53769`) - Disallow allowing logical operations (``||``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``); wrap the objects in :class:`Series`, :class:`Index`, or ``np.array`` first instead (:issue:`52264`) - Disallow automatic casting to object in :class:`Series` logical operations (``&``, ``^``, ``||``) between series with mismatched indexes and dtypes other than ``object`` or ``bool`` (:issue:`52538`) - Disallow calling :meth:`Series.replace` or :meth:`DataFrame.replace` without a ``value`` and with non-dict-like ``to_replace`` (:issue:`33302`) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index b3ae53272cae4..d29bef02a16cf 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -159,7 +159,7 @@ def iloc(self) -> _iLocIndexer: """ Purely integer-location based indexing for selection by position. - .. deprecated:: 2.2.0 + .. versionchanged:: 3.0 Returning a tuple from a callable is deprecated. @@ -905,7 +905,7 @@ def __setitem__(self, key, value) -> None: key = tuple(com.apply_if_callable(x, self.obj) for x in key) else: maybe_callable = com.apply_if_callable(key, self.obj) - key = self._check_deprecated_callable_usage(key, maybe_callable) + key = self._raise_callable_usage(key, maybe_callable) indexer = self._get_setitem_indexer(key) self._has_valid_setitem_indexer(key) @@ -1164,14 +1164,11 @@ def _contains_slice(x: object) -> bool: def _convert_to_indexer(self, key, axis: AxisInt): raise AbstractMethodError(self) - def _check_deprecated_callable_usage(self, key: Any, maybe_callable: T) -> T: + def _raise_callable_usage(self, key: Any, maybe_callable: T) -> T: # GH53533 if self.name == "iloc" and callable(key) and isinstance(maybe_callable, tuple): - warnings.warn( - "Returning a tuple from a callable with iloc " - "is deprecated and will be removed in a future version", - FutureWarning, - stacklevel=find_stack_level(), + raise ValueError( + "Returning a tuple from a callable with iloc is not allowed.", ) return maybe_callable @@ -1189,7 +1186,7 @@ def __getitem__(self, key): axis = self.axis or 0 maybe_callable = com.apply_if_callable(key, self.obj) - maybe_callable = self._check_deprecated_callable_usage(key, maybe_callable) + maybe_callable = self._raise_callable_usage(key, maybe_callable) return self._getitem_axis(maybe_callable, axis=axis) def _is_scalar_access(self, key: tuple): diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py index ee08c10f96ae7..bc233c5b86b4b 100644 --- a/pandas/tests/frame/indexing/test_indexing.py +++ b/pandas/tests/frame/indexing/test_indexing.py @@ -1019,13 +1019,13 @@ def test_single_element_ix_dont_upcast(self, float_frame): result = df.loc[[0], "b"] tm.assert_series_equal(result, expected) - def test_iloc_callable_tuple_return_value(self): - # GH53769 + def test_iloc_callable_tuple_return_value_raises(self): + # GH53769: Enforced pandas 3.0 df = DataFrame(np.arange(40).reshape(10, 4), index=range(0, 20, 2)) - msg = "callable with iloc" - with tm.assert_produces_warning(FutureWarning, match=msg): + msg = "Returning a tuple from" + with pytest.raises(ValueError, match=msg): df.iloc[lambda _: (0,)] - with tm.assert_produces_warning(FutureWarning, match=msg): + with pytest.raises(ValueError, match=msg): df.iloc[lambda _: (0,)] = 1 def test_iloc_row(self):