diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 75aec514031b4..f6d6dd7f90b02 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -259,6 +259,9 @@ Copy-on-Write improvements - :meth:`DataFrame.replace` will now respect the Copy-on-Write mechanism when ``inplace=True``. +- Arithmetic operations that can be inplace, e.g. ``ser *= 2`` will now respect the + Copy-on-Write mechanism. + Copy-on-Write can be enabled through one of .. code-block:: python diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 78b0a8a7a6ded..96abd98bbdd75 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -11737,7 +11737,11 @@ def _inplace_method(self, other, op): and is_dtype_equal(result.dtype, self.dtype) ): # GH#36498 this inplace op can _actually_ be inplace. - self._values[:] = result._values + # Item "ArrayManager" of "Union[ArrayManager, SingleArrayManager, + # BlockManager, SingleBlockManager]" has no attribute "setitem_inplace" + self._mgr.setitem_inplace( # type: ignore[union-attr] + slice(None), result._values + ) return self # Delete cacher diff --git a/pandas/tests/copy_view/test_methods.py b/pandas/tests/copy_view/test_methods.py index eca827bd60e0d..91419ba415fda 100644 --- a/pandas/tests/copy_view/test_methods.py +++ b/pandas/tests/copy_view/test_methods.py @@ -1479,3 +1479,23 @@ def test_xs_multiindex(using_copy_on_write, using_array_manager, key, level, axi result.iloc[0, 0] = 0 tm.assert_frame_equal(df, df_orig) + + +def test_inplace_arithmetic_series(): + ser = Series([1, 2, 3]) + data = get_array(ser) + ser *= 2 + assert np.shares_memory(get_array(ser), data) + tm.assert_numpy_array_equal(data, get_array(ser)) + + +def test_inplace_arithmetic_series_with_reference(using_copy_on_write): + ser = Series([1, 2, 3]) + ser_orig = ser.copy() + view = ser[:] + ser *= 2 + if using_copy_on_write: + assert not np.shares_memory(get_array(ser), get_array(view)) + tm.assert_series_equal(ser_orig, view) + else: + assert np.shares_memory(get_array(ser), get_array(view)) diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index d2d2b66df36d7..bcc1ae0183b97 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -1988,17 +1988,22 @@ def test_arith_list_of_arraylike_raise(to_add): to_add + df -def test_inplace_arithmetic_series_update(): +def test_inplace_arithmetic_series_update(using_copy_on_write): # https://github.com/pandas-dev/pandas/issues/36373 df = DataFrame({"A": [1, 2, 3]}) + df_orig = df.copy() series = df["A"] vals = series._values series += 1 - assert series._values is vals + if using_copy_on_write: + assert series._values is not vals + tm.assert_frame_equal(df, df_orig) + else: + assert series._values is vals - expected = DataFrame({"A": [2, 3, 4]}) - tm.assert_frame_equal(df, expected) + expected = DataFrame({"A": [2, 3, 4]}) + tm.assert_frame_equal(df, expected) def test_arithemetic_multiindex_align(): diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index 2243879050c0c..44c0b654db268 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -1105,9 +1105,12 @@ def test_identity_slice_returns_new_object(self, using_copy_on_write): else: assert all(sliced_series[:3] == [7, 8, 9]) - @pytest.mark.xfail(reason="accidental fix reverted - GH37497") - def test_loc_copy_vs_view(self): + def test_loc_copy_vs_view(self, request, using_copy_on_write): # GH 15631 + + if not using_copy_on_write: + mark = pytest.mark.xfail(reason="accidental fix reverted - GH37497") + request.node.add_marker(mark) x = DataFrame(zip(range(3), range(3)), columns=["a", "b"]) y = x.copy()