Skip to content

Commit 0802f06

Browse files
authored
DEPR: logical operations with dtype-less sequences (#52264)
1 parent 1e03419 commit 0802f06

File tree

4 files changed

+62
-9
lines changed

4 files changed

+62
-9
lines changed

doc/source/whatsnew/v2.1.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Deprecations
115115
- Deprecated 'method', 'limit', and 'fill_axis' keywords in :meth:`DataFrame.align` and :meth:`Series.align`, explicitly call ``fillna`` on the alignment results instead (:issue:`51856`)
116116
- Deprecated 'broadcast_axis' keyword in :meth:`Series.align` and :meth:`DataFrame.align`, upcast before calling ``align`` with ``left = DataFrame({col: left for col in right.columns}, index=right.index)`` (:issue:`51856`)
117117
- Deprecated the 'axis' keyword in :meth:`.GroupBy.idxmax`, :meth:`.GroupBy.idxmin`, :meth:`.GroupBy.fillna`, :meth:`.GroupBy.take`, :meth:`.GroupBy.skew`, :meth:`.GroupBy.rank`, :meth:`.GroupBy.cumprod`, :meth:`.GroupBy.cumsum`, :meth:`.GroupBy.cummax`, :meth:`.GroupBy.cummin`, :meth:`.GroupBy.pct_change`, :meth:`GroupBy.diff`, :meth:`.GroupBy.shift`, and :meth:`DataFrameGroupBy.corrwith`; for ``axis=1`` operate on the underlying :class:`DataFrame` instead (:issue:`50405`, :issue:`51046`)
118+
- Deprecated logical operations (``|``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``), wrap a sequence in a :class:`Series` or numpy array before operating instead (:issue:`51521`)
118119
- Deprecated :meth:`DataFrame.swapaxes` and :meth:`Series.swapaxes`, use :meth:`DataFrame.transpose` or :meth:`Series.transpose` instead (:issue:`51946`)
119120
-
120121

pandas/core/ops/array_ops.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
TYPE_CHECKING,
1212
Any,
1313
)
14+
import warnings
1415

1516
import numpy as np
1617

@@ -22,6 +23,7 @@
2223
ops as libops,
2324
)
2425
from pandas._libs.tslibs import BaseOffset
26+
from pandas.util._exceptions import find_stack_level
2527

2628
from pandas.core.dtypes.cast import (
2729
construct_1d_object_array_from_listlike,
@@ -416,6 +418,14 @@ def fill_bool(x, left=None):
416418
right = lib.item_from_zerodim(right)
417419
if is_list_like(right) and not hasattr(right, "dtype"):
418420
# e.g. list, tuple
421+
warnings.warn(
422+
"Logical ops (and, or, xor) between Pandas objects and dtype-less "
423+
"sequences (e.g. list, tuple) are deprecated and will raise in a "
424+
"future version. Wrap the object in a Series, Index, or np.array "
425+
"before operating instead.",
426+
FutureWarning,
427+
stacklevel=find_stack_level(),
428+
)
419429
right = construct_1d_object_array_from_listlike(right)
420430

421431
# NB: We assume extract_array has already been called on left and right

pandas/tests/series/test_arithmetic.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,14 @@ def test_series_ops_name_retention(self, flex, box, names, all_binary_operators)
801801
name = op.__name__.strip("_")
802802
is_logical = name in ["and", "rand", "xor", "rxor", "or", "ror"]
803803

804+
msg = (
805+
r"Logical ops \(and, or, xor\) between Pandas objects and "
806+
"dtype-less sequences"
807+
)
808+
warn = None
809+
if box in [list, tuple] and is_logical:
810+
warn = FutureWarning
811+
804812
right = box(right)
805813
if flex:
806814
if is_logical:
@@ -809,7 +817,8 @@ def test_series_ops_name_retention(self, flex, box, names, all_binary_operators)
809817
result = getattr(left, name)(right)
810818
else:
811819
# GH#37374 logical ops behaving as set ops deprecated
812-
result = op(left, right)
820+
with tm.assert_produces_warning(warn, match=msg):
821+
result = op(left, right)
813822

814823
assert isinstance(result, Series)
815824
if box in [Index, Series]:

pandas/tests/series/test_logical_ops.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,39 @@ def test_logical_operators_int_dtype_with_float(self):
8686
# GH#9016: support bitwise op for integer types
8787
s_0123 = Series(range(4), dtype="int64")
8888

89+
warn_msg = (
90+
r"Logical ops \(and, or, xor\) between Pandas objects and "
91+
"dtype-less sequences"
92+
)
93+
8994
msg = "Cannot perform.+with a dtyped.+array and scalar of type"
9095
with pytest.raises(TypeError, match=msg):
9196
s_0123 & np.NaN
9297
with pytest.raises(TypeError, match=msg):
9398
s_0123 & 3.14
9499
msg = "unsupported operand type.+for &:"
95100
with pytest.raises(TypeError, match=msg):
96-
s_0123 & [0.1, 4, 3.14, 2]
101+
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
102+
s_0123 & [0.1, 4, 3.14, 2]
97103
with pytest.raises(TypeError, match=msg):
98104
s_0123 & np.array([0.1, 4, 3.14, 2])
99105
with pytest.raises(TypeError, match=msg):
100106
s_0123 & Series([0.1, 4, -3.14, 2])
101107

102108
def test_logical_operators_int_dtype_with_str(self):
103109
s_1111 = Series([1] * 4, dtype="int8")
110+
111+
warn_msg = (
112+
r"Logical ops \(and, or, xor\) between Pandas objects and "
113+
"dtype-less sequences"
114+
)
115+
104116
msg = "Cannot perform 'and_' with a dtyped.+array and scalar of type"
105117
with pytest.raises(TypeError, match=msg):
106118
s_1111 & "a"
107119
with pytest.raises(TypeError, match="unsupported operand.+for &"):
108-
s_1111 & ["a", "b", "c", "d"]
120+
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
121+
s_1111 & ["a", "b", "c", "d"]
109122

110123
def test_logical_operators_int_dtype_with_bool(self):
111124
# GH#9016: support bitwise op for integer types
@@ -116,10 +129,16 @@ def test_logical_operators_int_dtype_with_bool(self):
116129
result = s_0123 & False
117130
tm.assert_series_equal(result, expected)
118131

119-
result = s_0123 & [False]
132+
warn_msg = (
133+
r"Logical ops \(and, or, xor\) between Pandas objects and "
134+
"dtype-less sequences"
135+
)
136+
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
137+
result = s_0123 & [False]
120138
tm.assert_series_equal(result, expected)
121139

122-
result = s_0123 & (False,)
140+
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
141+
result = s_0123 & (False,)
123142
tm.assert_series_equal(result, expected)
124143

125144
result = s_0123 ^ False
@@ -157,8 +176,14 @@ def test_logical_ops_bool_dtype_with_ndarray(self):
157176
left = Series([True, True, True, False, True])
158177
right = [True, False, None, True, np.nan]
159178

179+
msg = (
180+
r"Logical ops \(and, or, xor\) between Pandas objects and "
181+
"dtype-less sequences"
182+
)
183+
160184
expected = Series([True, False, False, False, False])
161-
result = left & right
185+
with tm.assert_produces_warning(FutureWarning, match=msg):
186+
result = left & right
162187
tm.assert_series_equal(result, expected)
163188
result = left & np.array(right)
164189
tm.assert_series_equal(result, expected)
@@ -168,7 +193,8 @@ def test_logical_ops_bool_dtype_with_ndarray(self):
168193
tm.assert_series_equal(result, expected)
169194

170195
expected = Series([True, True, True, True, True])
171-
result = left | right
196+
with tm.assert_produces_warning(FutureWarning, match=msg):
197+
result = left | right
172198
tm.assert_series_equal(result, expected)
173199
result = left | np.array(right)
174200
tm.assert_series_equal(result, expected)
@@ -178,7 +204,8 @@ def test_logical_ops_bool_dtype_with_ndarray(self):
178204
tm.assert_series_equal(result, expected)
179205

180206
expected = Series([False, True, True, True, True])
181-
result = left ^ right
207+
with tm.assert_produces_warning(FutureWarning, match=msg):
208+
result = left ^ right
182209
tm.assert_series_equal(result, expected)
183210
result = left ^ np.array(right)
184211
tm.assert_series_equal(result, expected)
@@ -231,7 +258,13 @@ def test_scalar_na_logical_ops_corners(self):
231258

232259
expected = Series(True, index=s.index)
233260
expected[::2] = False
234-
result = s & list(s)
261+
262+
msg = (
263+
r"Logical ops \(and, or, xor\) between Pandas objects and "
264+
"dtype-less sequences"
265+
)
266+
with tm.assert_produces_warning(FutureWarning, match=msg):
267+
result = s & list(s)
235268
tm.assert_series_equal(result, expected)
236269

237270
def test_scalar_na_logical_ops_corners_aligns(self):

0 commit comments

Comments
 (0)