diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 50643454bbcec..f709bec842c86 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -208,6 +208,7 @@ Removal of prior version deprecations/changes - :meth:`SeriesGroupBy.agg` no longer pins the name of the group to the input passed to the provided ``func`` (:issue:`51703`) - 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`) +- 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`) - Disallow constructing a :class:`arrays.SparseArray` with scalar data (:issue:`53039`) diff --git a/pandas/core/ops/array_ops.py b/pandas/core/ops/array_ops.py index 810e30d369729..983a3df57e369 100644 --- a/pandas/core/ops/array_ops.py +++ b/pandas/core/ops/array_ops.py @@ -12,7 +12,6 @@ TYPE_CHECKING, Any, ) -import warnings import numpy as np @@ -29,7 +28,6 @@ is_supported_dtype, is_unitless, ) -from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.cast import ( construct_1d_object_array_from_listlike, @@ -424,15 +422,13 @@ def fill_bool(x, left=None): right = lib.item_from_zerodim(right) if is_list_like(right) and not hasattr(right, "dtype"): # e.g. list, tuple - warnings.warn( + raise TypeError( + # GH#52264 "Logical ops (and, or, xor) between Pandas objects and dtype-less " - "sequences (e.g. list, tuple) are deprecated and will raise in a " - "future version. Wrap the object in a Series, Index, or np.array " + "sequences (e.g. list, tuple) are no longer supported. " + "Wrap the object in a Series, Index, or np.array " "before operating instead.", - FutureWarning, - stacklevel=find_stack_level(), ) - right = construct_1d_object_array_from_listlike(right) # NB: We assume extract_array has already been called on left and right lvalues = ensure_wrapped_if_datetimelike(left) diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index 00f48bf3b1d78..44bf3475b85a6 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -807,9 +807,6 @@ def test_series_ops_name_retention(self, flex, box, names, all_binary_operators) r"Logical ops \(and, or, xor\) between Pandas objects and " "dtype-less sequences" ) - warn = None - if box in [list, tuple] and is_logical: - warn = FutureWarning right = box(right) if flex: @@ -818,9 +815,12 @@ def test_series_ops_name_retention(self, flex, box, names, all_binary_operators) return result = getattr(left, name)(right) else: - # GH#37374 logical ops behaving as set ops deprecated - with tm.assert_produces_warning(warn, match=msg): - result = op(left, right) + if is_logical and box in [list, tuple]: + with pytest.raises(TypeError, match=msg): + # GH#52264 logical ops with dtype-less sequences deprecated + op(left, right) + return + result = op(left, right) assert isinstance(result, Series) if box in [Index, Series]: diff --git a/pandas/tests/series/test_logical_ops.py b/pandas/tests/series/test_logical_ops.py index dfc3309a8e449..f59eacea3fe6c 100644 --- a/pandas/tests/series/test_logical_ops.py +++ b/pandas/tests/series/test_logical_ops.py @@ -86,7 +86,7 @@ def test_logical_operators_int_dtype_with_float(self): # GH#9016: support bitwise op for integer types s_0123 = Series(range(4), dtype="int64") - warn_msg = ( + err_msg = ( r"Logical ops \(and, or, xor\) between Pandas objects and " "dtype-less sequences" ) @@ -97,9 +97,8 @@ def test_logical_operators_int_dtype_with_float(self): with pytest.raises(TypeError, match=msg): s_0123 & 3.14 msg = "unsupported operand type.+for &:" - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning, match=warn_msg): - s_0123 & [0.1, 4, 3.14, 2] + with pytest.raises(TypeError, match=err_msg): + s_0123 & [0.1, 4, 3.14, 2] with pytest.raises(TypeError, match=msg): s_0123 & np.array([0.1, 4, 3.14, 2]) with pytest.raises(TypeError, match=msg): @@ -108,7 +107,7 @@ def test_logical_operators_int_dtype_with_float(self): def test_logical_operators_int_dtype_with_str(self): s_1111 = Series([1] * 4, dtype="int8") - warn_msg = ( + err_msg = ( r"Logical ops \(and, or, xor\) between Pandas objects and " "dtype-less sequences" ) @@ -116,9 +115,8 @@ def test_logical_operators_int_dtype_with_str(self): msg = "Cannot perform 'and_' with a dtyped.+array and scalar of type" with pytest.raises(TypeError, match=msg): s_1111 & "a" - with pytest.raises(TypeError, match="unsupported operand.+for &"): - with tm.assert_produces_warning(FutureWarning, match=warn_msg): - s_1111 & ["a", "b", "c", "d"] + with pytest.raises(TypeError, match=err_msg): + s_1111 & ["a", "b", "c", "d"] def test_logical_operators_int_dtype_with_bool(self): # GH#9016: support bitwise op for integer types @@ -129,17 +127,15 @@ def test_logical_operators_int_dtype_with_bool(self): result = s_0123 & False tm.assert_series_equal(result, expected) - warn_msg = ( + msg = ( r"Logical ops \(and, or, xor\) between Pandas objects and " "dtype-less sequences" ) - with tm.assert_produces_warning(FutureWarning, match=warn_msg): - result = s_0123 & [False] - tm.assert_series_equal(result, expected) + with pytest.raises(TypeError, match=msg): + s_0123 & [False] - with tm.assert_produces_warning(FutureWarning, match=warn_msg): - result = s_0123 & (False,) - tm.assert_series_equal(result, expected) + with pytest.raises(TypeError, match=msg): + s_0123 & (False,) result = s_0123 ^ False expected = Series([False, True, True, True]) @@ -188,9 +184,8 @@ def test_logical_ops_bool_dtype_with_ndarray(self): ) expected = Series([True, False, False, False, False]) - with tm.assert_produces_warning(FutureWarning, match=msg): - result = left & right - tm.assert_series_equal(result, expected) + with pytest.raises(TypeError, match=msg): + left & right result = left & np.array(right) tm.assert_series_equal(result, expected) result = left & Index(right) @@ -199,9 +194,8 @@ def test_logical_ops_bool_dtype_with_ndarray(self): tm.assert_series_equal(result, expected) expected = Series([True, True, True, True, True]) - with tm.assert_produces_warning(FutureWarning, match=msg): - result = left | right - tm.assert_series_equal(result, expected) + with pytest.raises(TypeError, match=msg): + left | right result = left | np.array(right) tm.assert_series_equal(result, expected) result = left | Index(right) @@ -210,9 +204,8 @@ def test_logical_ops_bool_dtype_with_ndarray(self): tm.assert_series_equal(result, expected) expected = Series([False, True, True, True, True]) - with tm.assert_produces_warning(FutureWarning, match=msg): - result = left ^ right - tm.assert_series_equal(result, expected) + with pytest.raises(TypeError, match=msg): + left ^ right result = left ^ np.array(right) tm.assert_series_equal(result, expected) result = left ^ Index(right) @@ -269,9 +262,8 @@ def test_scalar_na_logical_ops_corners(self): r"Logical ops \(and, or, xor\) between Pandas objects and " "dtype-less sequences" ) - with tm.assert_produces_warning(FutureWarning, match=msg): - result = s & list(s) - tm.assert_series_equal(result, expected) + with pytest.raises(TypeError, match=msg): + s & list(s) def test_scalar_na_logical_ops_corners_aligns(self): s = Series([2, 3, 4, 5, 6, 7, 8, 9, datetime(2005, 1, 1)])