Skip to content

Commit 1fcdba2

Browse files
CoW: change ChainedAssignmentError exception to a warning (#51926)
* CoW: change ChainedAssignmentError exception to a warning * update docs * update whatsnew
1 parent 3ea1780 commit 1fcdba2

File tree

8 files changed

+25
-17
lines changed

8 files changed

+25
-17
lines changed

doc/source/user_guide/copy_on_write.rst

Lines changed: 3 additions & 2 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ def raises_chained_assignment_error():
211211

212212
return nullcontext()
213213
else:
214-
import pytest
214+
from pandas._testing import assert_produces_warning
215215

216-
return pytest.raises(
216+
return assert_produces_warning(
217217
ChainedAssignmentError,
218218
match=(
219219
"A value is trying to be set on a copy of a DataFrame or Series "

pandas/core/frame.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3954,7 +3954,9 @@ def isetitem(self, loc, value) -> None:
39543954
def __setitem__(self, key, value):
39553955
if not PYPY and using_copy_on_write():
39563956
if sys.getrefcount(self) <= 3:
3957-
raise ChainedAssignmentError(_chained_assignment_msg)
3957+
warnings.warn(
3958+
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
3959+
)
39583960

39593961
key = com.apply_if_callable(key, self)
39603962

pandas/core/indexing.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
cast,
1010
final,
1111
)
12+
import warnings
1213

1314
import numpy as np
1415

@@ -828,7 +829,9 @@ def _ensure_listlike_indexer(self, key, axis=None, value=None) -> None:
828829
def __setitem__(self, key, value) -> None:
829830
if not PYPY and using_copy_on_write():
830831
if sys.getrefcount(self.obj) <= 2:
831-
raise ChainedAssignmentError(_chained_assignment_msg)
832+
warnings.warn(
833+
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
834+
)
832835

833836
check_dict_or_set_indexers(key)
834837
if isinstance(key, tuple):

pandas/core/series.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,9 @@ def _get_value(self, label, takeable: bool = False):
10921092
def __setitem__(self, key, value) -> None:
10931093
if not PYPY and using_copy_on_write():
10941094
if sys.getrefcount(self) <= 3:
1095-
raise ChainedAssignmentError(_chained_assignment_msg)
1095+
warnings.warn(
1096+
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
1097+
)
10961098

10971099
check_dict_or_set_indexers(key)
10981100
key = com.apply_if_callable(key, self)

pandas/errors/__init__.py

Lines changed: 2 additions & 2 deletions
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

Lines changed: 6 additions & 6 deletions
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)