Skip to content

Commit b791c06

Browse files
Backport PR pandas-dev#51926 on branch 2.0.x (CoW: change ChainedAssignmentError exception to a warning) (pandas-dev#51986)
Backport PR pandas-dev#51926: CoW: change ChainedAssignmentError exception to a warning Co-authored-by: Joris Van den Bossche <[email protected]>
1 parent f8571f2 commit b791c06

File tree

8 files changed

+25
-17
lines changed

8 files changed

+25
-17
lines changed

doc/source/user_guide/copy_on_write.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ two subsequent indexing operations, e.g.
114114
The column ``foo`` is updated where the column ``bar`` is greater than 5.
115115
This violates the CoW principles though, because it would have to modify the
116116
view ``df["foo"]`` and ``df`` in one step. Hence, chained assignment will
117-
consistently never work and raise a ``ChainedAssignmentError`` with CoW enabled:
117+
consistently never work and raise a ``ChainedAssignmentError`` warning
118+
with CoW enabled:
118119

119120
.. ipython:: python
120-
:okexcept:
121+
:okwarning:
121122
122123
df = pd.DataFrame({"foo": [1, 2, 3], "bar": [4, 5, 6]})
123124
df["foo"][df["bar"] > 5] = 100

doc/source/whatsnew/v2.0.0.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,12 @@ Copy-on-Write improvements
191191
of those Series objects for the columns of the DataFrame (:issue:`50777`)
192192

193193
- Trying to set values using chained assignment (for example, ``df["a"][1:3] = 0``)
194-
will now always raise an exception when Copy-on-Write is enabled. In this mode,
194+
will now always raise an warning when Copy-on-Write is enabled. In this mode,
195195
chained assignment can never work because we are always setting into a temporary
196196
object that is the result of an indexing operation (getitem), which under
197197
Copy-on-Write always behaves as a copy. Thus, assigning through a chain
198198
can never update the original Series or DataFrame. Therefore, an informative
199-
error is raised to the user instead of silently doing nothing (:issue:`49467`)
199+
warning is raised to the user to avoid silently doing nothing (:issue:`49467`)
200200

201201
- :meth:`DataFrame.replace` will now respect the Copy-on-Write mechanism
202202
when ``inplace=True``.

pandas/_testing/contexts.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,9 @@ def raises_chained_assignment_error():
208208

209209
return nullcontext()
210210
else:
211-
import pytest
211+
from pandas._testing import assert_produces_warning
212212

213-
return pytest.raises(
213+
return assert_produces_warning(
214214
ChainedAssignmentError,
215215
match=(
216216
"A value is trying to be set on a copy of a DataFrame or Series "

pandas/core/frame.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -3904,7 +3904,9 @@ def isetitem(self, loc, value) -> None:
39043904
def __setitem__(self, key, value):
39053905
if not PYPY and using_copy_on_write():
39063906
if sys.getrefcount(self) <= 3:
3907-
raise ChainedAssignmentError(_chained_assignment_msg)
3907+
warnings.warn(
3908+
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
3909+
)
39083910

39093911
key = com.apply_if_callable(key, self)
39103912

pandas/core/indexing.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
cast,
1111
final,
1212
)
13+
import warnings
1314

1415
import numpy as np
1516

@@ -831,7 +832,9 @@ def _ensure_listlike_indexer(self, key, axis=None, value=None) -> None:
831832
def __setitem__(self, key, value) -> None:
832833
if not PYPY and using_copy_on_write():
833834
if sys.getrefcount(self.obj) <= 2:
834-
raise ChainedAssignmentError(_chained_assignment_msg)
835+
warnings.warn(
836+
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
837+
)
835838

836839
check_dict_or_set_indexers(key)
837840
if isinstance(key, tuple):

pandas/core/series.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,9 @@ def _get_value(self, label, takeable: bool = False):
11211121
def __setitem__(self, key, value) -> None:
11221122
if not PYPY and using_copy_on_write():
11231123
if sys.getrefcount(self) <= 3:
1124-
raise ChainedAssignmentError(_chained_assignment_msg)
1124+
warnings.warn(
1125+
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
1126+
)
11251127

11261128
check_dict_or_set_indexers(key)
11271129
key = com.apply_if_callable(key, self)

pandas/errors/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,9 @@ class SettingWithCopyWarning(Warning):
320320
"""
321321

322322

323-
class ChainedAssignmentError(ValueError):
323+
class ChainedAssignmentError(Warning):
324324
"""
325-
Exception raised when trying to set using chained assignment.
325+
Warning raised when trying to set using chained assignment.
326326
327327
When the ``mode.copy_on_write`` option is enabled, chained assignment can
328328
never work. In such a situation, we are always setting into a temporary

pandas/tests/io/test_spss.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import numpy as np
44
import pytest
55

6-
import pandas.util._test_decorators as td
7-
86
import pandas as pd
97
import pandas._testing as tm
108

119
pyreadstat = pytest.importorskip("pyreadstat")
1210

1311

14-
@td.skip_copy_on_write_not_yet_implemented
12+
# TODO(CoW) - detection of chained assignment in cython
13+
# https://github.com/pandas-dev/pandas/issues/51315
14+
@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
1515
@pytest.mark.parametrize("path_klass", [lambda p: p, Path])
1616
def test_spss_labelled_num(path_klass, datapath):
1717
# test file from the Haven project (https://haven.tidyverse.org/)
@@ -27,7 +27,7 @@ def test_spss_labelled_num(path_klass, datapath):
2727
tm.assert_frame_equal(df, expected)
2828

2929

30-
@td.skip_copy_on_write_not_yet_implemented
30+
@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
3131
def test_spss_labelled_num_na(datapath):
3232
# test file from the Haven project (https://haven.tidyverse.org/)
3333
fname = datapath("io", "data", "spss", "labelled-num-na.sav")
@@ -42,7 +42,7 @@ def test_spss_labelled_num_na(datapath):
4242
tm.assert_frame_equal(df, expected)
4343

4444

45-
@td.skip_copy_on_write_not_yet_implemented
45+
@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
4646
def test_spss_labelled_str(datapath):
4747
# test file from the Haven project (https://haven.tidyverse.org/)
4848
fname = datapath("io", "data", "spss", "labelled-str.sav")
@@ -57,7 +57,7 @@ def test_spss_labelled_str(datapath):
5757
tm.assert_frame_equal(df, expected)
5858

5959

60-
@td.skip_copy_on_write_not_yet_implemented
60+
@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
6161
def test_spss_umlauts(datapath):
6262
# test file from the Haven project (https://haven.tidyverse.org/)
6363
fname = datapath("io", "data", "spss", "umlauts.sav")

0 commit comments

Comments
 (0)