Skip to content

Commit 91551fa

Browse files
warn in case of chained assignment
1 parent 6493d2a commit 91551fa

File tree

6 files changed

+61
-39
lines changed

6 files changed

+61
-39
lines changed

pandas/core/frame.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from pandas._config import (
4343
get_option,
4444
using_copy_on_write,
45+
warn_copy_on_write,
4546
)
4647
from pandas._config.config import _get_option
4748

@@ -4197,11 +4198,13 @@ def isetitem(self, loc, value) -> None:
41974198
self._iset_item_mgr(loc, arraylike, inplace=False, refs=refs)
41984199

41994200
def __setitem__(self, key, value) -> None:
4200-
if not PYPY and using_copy_on_write():
4201-
if sys.getrefcount(self) <= 3:
4201+
if not PYPY:
4202+
if using_copy_on_write() and sys.getrefcount(self) <= 3:
42024203
warnings.warn(
42034204
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
42044205
)
4206+
elif warn_copy_on_write() and sys.getrefcount(self) <= 3:
4207+
warnings.warn("ChainedAssignmentError", FutureWarning, stacklevel=2)
42054208

42064209
key = com.apply_if_callable(key, self)
42074210

pandas/core/internals/managers.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,7 +2005,9 @@ def get_numeric_data(self, copy: bool = False) -> Self:
20052005
def _can_hold_na(self) -> bool:
20062006
return self._block._can_hold_na
20072007

2008-
def setitem_inplace(self, indexer, value, warn: bool = True) -> None:
2008+
def setitem_inplace(
2009+
self, indexer, value, warn: bool = True, cow_context=None
2010+
) -> None:
20092011
"""
20102012
Set values with indexer.
20112013
@@ -2022,11 +2024,28 @@ def setitem_inplace(self, indexer, value, warn: bool = True) -> None:
20222024
self.blocks = (self._block.copy(),)
20232025
self._cache.clear()
20242026
elif warn and warn_cow:
2025-
warnings.warn(
2026-
COW_WARNING_SETITEM_MSG,
2027-
FutureWarning,
2028-
stacklevel=find_stack_level(),
2029-
)
2027+
if cow_context == "chained-assignment":
2028+
warnings.warn(
2029+
"ChainedAssignmentError: behaviour will change in pandas 3.0 "
2030+
"with Copy-on-Write ...",
2031+
FutureWarning,
2032+
stacklevel=find_stack_level(),
2033+
)
2034+
else:
2035+
warnings.warn(
2036+
COW_WARNING_SETITEM_MSG,
2037+
FutureWarning,
2038+
stacklevel=find_stack_level(),
2039+
)
2040+
else:
2041+
if warn and warn_cow:
2042+
if cow_context == "chained-assignment":
2043+
warnings.warn(
2044+
"ChainedAssignmentError: behaviour will change in pandas 3.0 "
2045+
"with Copy-on-Write ...",
2046+
FutureWarning,
2047+
stacklevel=find_stack_level(),
2048+
)
20302049

20312050
super().setitem_inplace(indexer, value)
20322051

pandas/core/series.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626

2727
import numpy as np
2828

29-
from pandas._config import using_copy_on_write
29+
from pandas._config import (
30+
using_copy_on_write,
31+
warn_copy_on_write,
32+
)
3033
from pandas._config.config import _get_option
3134

3235
from pandas._libs import (
@@ -1229,11 +1232,14 @@ def _get_value(self, label, takeable: bool = False):
12291232
return self.iloc[loc]
12301233

12311234
def __setitem__(self, key, value) -> None:
1232-
if not PYPY and using_copy_on_write():
1233-
if sys.getrefcount(self) <= 3:
1235+
cow_context = None
1236+
if not PYPY:
1237+
if using_copy_on_write() and sys.getrefcount(self) <= 3:
12341238
warnings.warn(
12351239
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
12361240
)
1241+
elif warn_copy_on_write() and sys.getrefcount(self) <= 3:
1242+
cow_context = "chained-assignment"
12371243

12381244
check_dict_or_set_indexers(key)
12391245
key = com.apply_if_callable(key, self)
@@ -1247,7 +1253,7 @@ def __setitem__(self, key, value) -> None:
12471253
return self._set_values(indexer, value)
12481254

12491255
try:
1250-
self._set_with_engine(key, value)
1256+
self._set_with_engine(key, value, cow_context)
12511257
except KeyError:
12521258
# We have a scalar (or for MultiIndex or object-dtype, scalar-like)
12531259
# key that is not present in self.index.
@@ -1318,11 +1324,11 @@ def __setitem__(self, key, value) -> None:
13181324
if cacher_needs_updating:
13191325
self._maybe_update_cacher(inplace=True)
13201326

1321-
def _set_with_engine(self, key, value) -> None:
1327+
def _set_with_engine(self, key, value, cow_context=None) -> None:
13221328
loc = self.index.get_loc(key)
13231329

13241330
# this is equivalent to self._values[key] = value
1325-
self._mgr.setitem_inplace(loc, value)
1331+
self._mgr.setitem_inplace(loc, value, cow_context=cow_context)
13261332

13271333
def _set_with(self, key, value) -> None:
13281334
# We got here via exception-handling off of InvalidIndexError, so

pandas/tests/indexing/multiindex/test_setitem.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -539,8 +539,7 @@ def test_frame_setitem_copy_raises(
539539
with tm.raises_chained_assignment_error():
540540
df["foo"]["one"] = 2
541541
elif warn_copy_on_write:
542-
# TODO(CoW-warn) should warn
543-
with tm.assert_cow_warning(False):
542+
with tm.assert_produces_warning(FutureWarning, match="ChainedAssignmentError"):
544543
df["foo"]["one"] = 2
545544
else:
546545
msg = "A value is trying to be set on a copy of a slice from a DataFrame"
@@ -558,8 +557,7 @@ def test_frame_setitem_copy_no_write(
558557
with tm.raises_chained_assignment_error():
559558
df["foo"]["one"] = 2
560559
elif warn_copy_on_write:
561-
# TODO(CoW-warn) should warn
562-
with tm.assert_cow_warning(False):
560+
with tm.assert_produces_warning(FutureWarning, match="ChainedAssignmentError"):
563561
df["foo"]["one"] = 2
564562
else:
565563
msg = "A value is trying to be set on a copy of a slice from a DataFrame"

pandas/tests/indexing/test_chaining_and_caching.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,10 @@ def test_detect_chained_assignment(self, using_copy_on_write, warn_copy_on_write
213213
df["A"][0] = -5
214214
with tm.raises_chained_assignment_error():
215215
df["A"][1] = -6
216-
tm.assert_frame_equal(df, df_original)
216+
if using_copy_on_write:
217+
tm.assert_frame_equal(df, df_original)
218+
else:
219+
tm.assert_frame_equal(df, expected)
217220
else:
218221
with tm.assert_cow_warning(warn_copy_on_write):
219222
df["A"][0] = -5
@@ -279,8 +282,7 @@ def test_detect_chained_assignment_fails(
279282
with tm.raises_chained_assignment_error():
280283
df.loc[0]["A"] = -5
281284
elif warn_copy_on_write:
282-
# TODO(CoW-warn) should warn
283-
with tm.assert_cow_warning(False):
285+
with tm.assert_produces_warning(FutureWarning):
284286
df.loc[0]["A"] = -5
285287
else:
286288
with pytest.raises(SettingWithCopyError, match=msg):
@@ -304,8 +306,7 @@ def test_detect_chained_assignment_doc_example(
304306
with tm.raises_chained_assignment_error():
305307
df[indexer]["c"] = 42
306308
elif warn_copy_on_write:
307-
# TODO(CoW-warn) should warn
308-
with tm.assert_cow_warning(False):
309+
with tm.assert_produces_warning(FutureWarning):
309310
df[indexer]["c"] = 42
310311
else:
311312
with pytest.raises(SettingWithCopyError, match=msg):
@@ -328,8 +329,7 @@ def test_detect_chained_assignment_object_dtype(
328329
df["A"][0] = 111
329330
tm.assert_frame_equal(df, df_original)
330331
elif warn_copy_on_write:
331-
# TODO(CoW-warn) should give different message
332-
with tm.assert_cow_warning():
332+
with tm.assert_produces_warning(FutureWarning):
333333
df["A"][0] = 111
334334
tm.assert_frame_equal(df, expected)
335335
elif not using_array_manager:
@@ -458,8 +458,7 @@ def test_detect_chained_assignment_undefined_column(
458458
df.iloc[0:5]["group"] = "a"
459459
tm.assert_frame_equal(df, df_original)
460460
elif warn_copy_on_write:
461-
# TODO(CoW-warn) should warn
462-
with tm.assert_cow_warning(False):
461+
with tm.assert_produces_warning(FutureWarning):
463462
df.iloc[0:5]["group"] = "a"
464463
else:
465464
with pytest.raises(SettingWithCopyError, match=msg):
@@ -489,11 +488,9 @@ def test_detect_chained_assignment_changing_dtype(
489488
df["C"][2] = "foo"
490489
tm.assert_frame_equal(df, df_original)
491490
elif warn_copy_on_write:
492-
# TODO(CoW-warn) should warn
493-
with tm.assert_cow_warning(False):
491+
with tm.assert_produces_warning(FutureWarning):
494492
df.loc[2]["D"] = "foo"
495-
# TODO(CoW-warn) should give different message
496-
with tm.assert_cow_warning():
493+
with tm.assert_produces_warning(FutureWarning):
497494
df["C"][2] = "foo"
498495
else:
499496
with pytest.raises(SettingWithCopyError, match=msg):
@@ -524,8 +521,7 @@ def test_setting_with_copy_bug(self, using_copy_on_write, warn_copy_on_write):
524521
df[["c"]][mask] = df[["b"]][mask]
525522
tm.assert_frame_equal(df, df_original)
526523
elif warn_copy_on_write:
527-
# TODO(CoW-warn) should warn
528-
with tm.assert_cow_warning(False):
524+
with tm.assert_produces_warning(FutureWarning):
529525
df[["c"]][mask] = df[["b"]][mask]
530526
else:
531527
with pytest.raises(SettingWithCopyError, match=msg):
@@ -549,8 +545,7 @@ def test_detect_chained_assignment_warnings_errors(
549545
df.loc[0]["A"] = 111
550546
return
551547
elif warn_copy_on_write:
552-
# TODO(CoW-warn) should warn
553-
with tm.assert_cow_warning(False):
548+
with tm.assert_produces_warning(FutureWarning):
554549
df.loc[0]["A"] = 111
555550
return
556551

@@ -577,8 +572,8 @@ def test_detect_chained_assignment_warning_stacklevel(
577572
assert t[0].filename == __file__
578573
else:
579574
# INFO(CoW) no warning, and original dataframe not changed
580-
with tm.assert_produces_warning(None):
581-
chained[2] = rhs
575+
# with tm.assert_produces_warning(None):
576+
chained[2] = rhs
582577
tm.assert_frame_equal(df, df_original)
583578

584579
# TODO(ArrayManager) fast_xs with array-like scalars is not yet working

pandas/tests/series/accessors/test_dt_accessor.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,9 @@ def test_dt_accessor_not_writeable(self, using_copy_on_write, warn_copy_on_write
294294
with tm.raises_chained_assignment_error():
295295
ser.dt.hour[0] = 5
296296
elif warn_copy_on_write:
297-
# TODO(CoW-warn) should warn
298-
with tm.assert_cow_warning(False):
297+
with tm.assert_produces_warning(
298+
FutureWarning, match="ChainedAssignmentError"
299+
):
299300
ser.dt.hour[0] = 5
300301
else:
301302
with pytest.raises(SettingWithCopyError, match=msg):

0 commit comments

Comments
 (0)