Skip to content

Commit aa994bb

Browse files
authored
CoW: Fix deprecation warning for chained assignment (#56166)
1 parent 4eaf840 commit aa994bb

File tree

5 files changed

+91
-7
lines changed

5 files changed

+91
-7
lines changed

pandas/core/generic.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
SettingWithCopyWarning,
102102
_chained_assignment_method_msg,
103103
_chained_assignment_warning_method_msg,
104+
_check_cacher,
104105
)
105106
from pandas.util._decorators import (
106107
deprecate_nonkeyword_arguments,
@@ -7195,7 +7196,7 @@ def fillna(
71957196
elif not PYPY and not using_copy_on_write():
71967197
ctr = sys.getrefcount(self)
71977198
ref_count = REF_COUNT
7198-
if isinstance(self, ABCSeries) and hasattr(self, "_cacher"):
7199+
if isinstance(self, ABCSeries) and _check_cacher(self):
71997200
# see https://github.com/pandas-dev/pandas/pull/56060#discussion_r1399245221
72007201
ref_count += 1
72017202
if ctr <= ref_count:
@@ -7477,7 +7478,7 @@ def ffill(
74777478
elif not PYPY and not using_copy_on_write():
74787479
ctr = sys.getrefcount(self)
74797480
ref_count = REF_COUNT
7480-
if isinstance(self, ABCSeries) and hasattr(self, "_cacher"):
7481+
if isinstance(self, ABCSeries) and _check_cacher(self):
74817482
# see https://github.com/pandas-dev/pandas/pull/56060#discussion_r1399245221
74827483
ref_count += 1
74837484
if ctr <= ref_count:
@@ -7660,7 +7661,7 @@ def bfill(
76607661
elif not PYPY and not using_copy_on_write():
76617662
ctr = sys.getrefcount(self)
76627663
ref_count = REF_COUNT
7663-
if isinstance(self, ABCSeries) and hasattr(self, "_cacher"):
7664+
if isinstance(self, ABCSeries) and _check_cacher(self):
76647665
# see https://github.com/pandas-dev/pandas/pull/56060#discussion_r1399245221
76657666
ref_count += 1
76667667
if ctr <= ref_count:
@@ -7826,12 +7827,12 @@ def replace(
78267827
elif not PYPY and not using_copy_on_write():
78277828
ctr = sys.getrefcount(self)
78287829
ref_count = REF_COUNT
7829-
if isinstance(self, ABCSeries) and hasattr(self, "_cacher"):
7830+
if isinstance(self, ABCSeries) and _check_cacher(self):
78307831
# in non-CoW mode, chained Series access will populate the
78317832
# `_item_cache` which results in an increased ref count not below
78327833
# the threshold, while we still need to warn. We detect this case
78337834
# of a Series derived from a DataFrame through the presence of
7834-
# `_cacher`
7835+
# checking the `_cacher`
78357836
ref_count += 1
78367837
if ctr <= ref_count:
78377838
warnings.warn(
@@ -8267,7 +8268,7 @@ def interpolate(
82678268
elif not PYPY and not using_copy_on_write():
82688269
ctr = sys.getrefcount(self)
82698270
ref_count = REF_COUNT
8270-
if isinstance(self, ABCSeries) and hasattr(self, "_cacher"):
8271+
if isinstance(self, ABCSeries) and _check_cacher(self):
82718272
# see https://github.com/pandas-dev/pandas/pull/56060#discussion_r1399245221
82728273
ref_count += 1
82738274
if ctr <= ref_count:

pandas/core/series.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
_chained_assignment_method_msg,
4646
_chained_assignment_msg,
4747
_chained_assignment_warning_method_msg,
48+
_check_cacher,
4849
)
4950
from pandas.util._decorators import (
5051
Appender,
@@ -3544,7 +3545,7 @@ def update(self, other: Series | Sequence | Mapping) -> None:
35443545
elif not PYPY and not using_copy_on_write():
35453546
ctr = sys.getrefcount(self)
35463547
ref_count = REF_COUNT
3547-
if hasattr(self, "_cacher"):
3548+
if _check_cacher(self):
35483549
# see https://github.com/pandas-dev/pandas/pull/56060#discussion_r1399245221
35493550
ref_count += 1
35503551
if ctr <= ref_count:

pandas/errors/__init__.py

+18
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,24 @@ class ChainedAssignmentError(Warning):
516516
)
517517

518518

519+
def _check_cacher(obj):
520+
# This is a mess, selection paths that return a view set the _cacher attribute
521+
# on the Series; most of them also set _item_cache which adds 1 to our relevant
522+
# reference count, but iloc does not, so we have to check if we are actually
523+
# in the item cache
524+
if hasattr(obj, "_cacher"):
525+
parent = obj._cacher[1]()
526+
# parent could be dead
527+
if parent is None:
528+
return False
529+
if hasattr(parent, "_item_cache"):
530+
if obj._cacher[0] in parent._item_cache:
531+
# Check if we are actually the item from item_cache, iloc creates a
532+
# new object
533+
return obj is parent._item_cache[obj._cacher[0]]
534+
return False
535+
536+
519537
class NumExprClobberingError(NameError):
520538
"""
521539
Exception raised when trying to use a built-in numexpr name as a variable name.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import pytest
2+
3+
from pandas import DataFrame
4+
import pandas._testing as tm
5+
6+
7+
def test_methods_iloc_warn(using_copy_on_write):
8+
if not using_copy_on_write:
9+
df = DataFrame({"a": [1, 2, 3], "b": 1})
10+
with tm.assert_cow_warning(match="A value"):
11+
df.iloc[:, 0].replace(1, 5, inplace=True)
12+
13+
with tm.assert_cow_warning(match="A value"):
14+
df.iloc[:, 0].fillna(1, inplace=True)
15+
16+
with tm.assert_cow_warning(match="A value"):
17+
df.iloc[:, 0].interpolate(inplace=True)
18+
19+
with tm.assert_cow_warning(match="A value"):
20+
df.iloc[:, 0].ffill(inplace=True)
21+
22+
with tm.assert_cow_warning(match="A value"):
23+
df.iloc[:, 0].bfill(inplace=True)
24+
25+
26+
@pytest.mark.parametrize(
27+
"func, args",
28+
[
29+
("replace", (1, 5)),
30+
("fillna", (1,)),
31+
("interpolate", ()),
32+
("bfill", ()),
33+
("ffill", ()),
34+
],
35+
)
36+
def test_methods_iloc_getitem_item_cache(func, args, using_copy_on_write):
37+
df = DataFrame({"a": [1, 2, 3], "b": 1})
38+
ser = df.iloc[:, 0]
39+
TODO(CoW-warn) should warn about updating a view
40+
getattr(ser, func)(*args, inplace=True)
41+
42+
# parent that holds item_cache is dead, so don't increase ref count
43+
ser = df.copy()["a"]
44+
getattr(ser, func)(*args, inplace=True)
45+
46+
df = df.copy()
47+
48+
df["a"] # populate the item_cache
49+
ser = df.iloc[:, 0] # iloc creates a new object
50+
ser.fillna(0, inplace=True)
51+
52+
df["a"] # populate the item_cache
53+
ser = df["a"]
54+
ser.fillna(0, inplace=True)
55+
56+
df = df.copy()
57+
df["a"] # populate the item_cache
58+
if using_copy_on_write:
59+
with tm.raises_chained_assignment_error():
60+
df["a"].fillna(0, inplace=True)
61+
else:
62+
with tm.assert_cow_warning(match="A value"):
63+
df["a"].fillna(0, inplace=True)

scripts/validate_unwanted_patterns.py

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"_chained_assignment_msg",
5252
"_chained_assignment_method_msg",
5353
"_chained_assignment_warning_method_msg",
54+
"_check_cacher",
5455
"_version_meson",
5556
# The numba extensions need this to mock the iloc object
5657
"_iLocIndexer",

0 commit comments

Comments
 (0)