Skip to content

Commit 926403d

Browse files
authored
CoW: Add ChainedAssignmentError for update (#54024)
1 parent 2b60828 commit 926403d

File tree

8 files changed

+44
-9
lines changed

8 files changed

+44
-9
lines changed

doc/source/whatsnew/v2.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Copy-on-Write improvements
3333
operating inplace like this will never work, since the selection behaves
3434
as a temporary copy. This holds true for:
3535

36+
- DataFrame.update / Series.update
3637
- DataFrame.fillna / Series.fillna
3738

3839
.. _whatsnew_210.enhancements.enhancement2:

pandas/compat/_constants.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
PY311 = sys.version_info >= (3, 11)
1818
PYPY = platform.python_implementation() == "PyPy"
1919
ISMUSL = "musl" in (sysconfig.get_config_var("HOST_GNU_TYPE") or "")
20-
20+
REF_COUNT = 2 if PY311 else 3
2121

2222
__all__ = [
2323
"IS64",

pandas/core/frame.py

+10
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from pandas._libs.hashtable import duplicated
5252
from pandas._libs.lib import is_range_indexer
5353
from pandas.compat import PYPY
54+
from pandas.compat._constants import REF_COUNT
5455
from pandas.compat._optional import import_optional_dependency
5556
from pandas.compat.numpy import (
5657
function as nv,
@@ -59,6 +60,7 @@
5960
from pandas.errors import (
6061
ChainedAssignmentError,
6162
InvalidIndexError,
63+
_chained_assignment_method_msg,
6264
_chained_assignment_msg,
6365
)
6466
from pandas.util._decorators import (
@@ -8496,6 +8498,14 @@ def update(
84968498
1 2 500
84978499
2 3 6
84988500
"""
8501+
if not PYPY and using_copy_on_write():
8502+
if sys.getrefcount(self) <= REF_COUNT:
8503+
warnings.warn(
8504+
_chained_assignment_method_msg,
8505+
ChainedAssignmentError,
8506+
stacklevel=2,
8507+
)
8508+
84998509
from pandas.core.computation import expressions
85008510

85018511
# TODO: Support other joins

pandas/core/generic.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,8 @@
8585
WriteExcelBuffer,
8686
npt,
8787
)
88-
from pandas.compat import (
89-
PY311,
90-
PYPY,
91-
)
88+
from pandas.compat import PYPY
89+
from pandas.compat._constants import REF_COUNT
9290
from pandas.compat._optional import import_optional_dependency
9391
from pandas.compat.numpy import function as nv
9492
from pandas.errors import (
@@ -7093,8 +7091,7 @@ def fillna(
70937091
inplace = validate_bool_kwarg(inplace, "inplace")
70947092
if inplace:
70957093
if not PYPY and using_copy_on_write():
7096-
refcount = 2 if PY311 else 3
7097-
if sys.getrefcount(self) <= refcount:
7094+
if sys.getrefcount(self) <= REF_COUNT:
70987095
warnings.warn(
70997096
_chained_assignment_method_msg,
71007097
ChainedAssignmentError,

pandas/core/series.py

+9
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@
3939
)
4040
from pandas._libs.lib import is_range_indexer
4141
from pandas.compat import PYPY
42+
from pandas.compat._constants import REF_COUNT
4243
from pandas.compat.numpy import function as nv
4344
from pandas.errors import (
4445
ChainedAssignmentError,
4546
InvalidIndexError,
47+
_chained_assignment_method_msg,
4648
_chained_assignment_msg,
4749
)
4850
from pandas.util._decorators import (
@@ -3432,6 +3434,13 @@ def update(self, other: Series | Sequence | Mapping) -> None:
34323434
2 3
34333435
dtype: int64
34343436
"""
3437+
if not PYPY and using_copy_on_write():
3438+
if sys.getrefcount(self) <= REF_COUNT:
3439+
warnings.warn(
3440+
_chained_assignment_method_msg,
3441+
ChainedAssignmentError,
3442+
stacklevel=2,
3443+
)
34353444

34363445
if not isinstance(other, Series):
34373446
other = Series(other)

pandas/tests/copy_view/test_methods.py

+14
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,20 @@ def test_update_series(using_copy_on_write):
17311731
tm.assert_series_equal(view, expected)
17321732

17331733

1734+
def test_update_chained_assignment(using_copy_on_write):
1735+
df = DataFrame({"a": [1, 2, 3]})
1736+
ser2 = Series([100.0], index=[1])
1737+
df_orig = df.copy()
1738+
if using_copy_on_write:
1739+
with tm.raises_chained_assignment_error():
1740+
df["a"].update(ser2)
1741+
tm.assert_frame_equal(df, df_orig)
1742+
1743+
with tm.raises_chained_assignment_error():
1744+
df[["a"]].update(ser2.to_frame())
1745+
tm.assert_frame_equal(df, df_orig)
1746+
1747+
17341748
def test_inplace_arithmetic_series():
17351749
ser = Series([1, 2, 3])
17361750
data = get_array(ser)

pandas/tests/series/indexing/test_indexing.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,13 @@ def test_underlying_data_conversion(using_copy_on_write):
284284
df["val"] = 0
285285
df_original = df.copy()
286286
df
287-
df["val"].update(s)
288287

289288
if using_copy_on_write:
289+
with tm.raises_chained_assignment_error():
290+
df["val"].update(s)
290291
expected = df_original
291292
else:
293+
df["val"].update(s)
292294
expected = DataFrame(
293295
{"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3], "val": [0, 1, 0]}
294296
)

pandas/tests/series/methods/test_update.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ def test_update(self, using_copy_on_write):
2929
df["c"] = df["c"].astype(object)
3030
df_orig = df.copy()
3131

32-
df["c"].update(Series(["foo"], index=[0]))
3332
if using_copy_on_write:
33+
with tm.raises_chained_assignment_error():
34+
df["c"].update(Series(["foo"], index=[0]))
3435
expected = df_orig
3536
else:
37+
df["c"].update(Series(["foo"], index=[0]))
3638
expected = DataFrame(
3739
[[1, np.nan, "foo"], [3, 2.0, np.nan]], columns=["a", "b", "c"]
3840
)

0 commit comments

Comments
 (0)