diff --git a/pandas/_config/__init__.py b/pandas/_config/__init__.py index daeb135f5bcf7..97784c924dab4 100644 --- a/pandas/_config/__init__.py +++ b/pandas/_config/__init__.py @@ -15,6 +15,7 @@ "option_context", "options", "using_copy_on_write", + "warn_copy_on_write", ] from pandas._config import config from pandas._config import dates # pyright: ignore[reportUnusedImport] # noqa: F401 @@ -32,7 +33,18 @@ def using_copy_on_write() -> bool: _mode_options = _global_config["mode"] - return _mode_options["copy_on_write"] and _mode_options["data_manager"] == "block" + return ( + _mode_options["copy_on_write"] is True + and _mode_options["data_manager"] == "block" + ) + + +def warn_copy_on_write() -> bool: + _mode_options = _global_config["mode"] + return ( + _mode_options["copy_on_write"] == "warn" + and _mode_options["data_manager"] == "block" + ) def using_nullable_dtypes() -> bool: diff --git a/pandas/conftest.py b/pandas/conftest.py index 62f22921f0482..92a6d9e033bc3 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -1994,7 +1994,18 @@ def using_copy_on_write() -> bool: Fixture to check if Copy-on-Write is enabled. """ return ( - pd.options.mode.copy_on_write + pd.options.mode.copy_on_write is True + and _get_option("mode.data_manager", silent=True) == "block" + ) + + +@pytest.fixture +def warn_copy_on_write() -> bool: + """ + Fixture to check if Copy-on-Write is enabled. + """ + return ( + pd.options.mode.copy_on_write == "warn" and _get_option("mode.data_manager", silent=True) == "block" ) diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 4652acdcae287..a8b63f97141c2 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -476,9 +476,11 @@ def use_inf_as_na_cb(key) -> None: "copy_on_write", # Get the default from an environment variable, if set, otherwise defaults # to False. This environment variable can be set for testing. - os.environ.get("PANDAS_COPY_ON_WRITE", "0") == "1", + "warn" + if os.environ.get("PANDAS_COPY_ON_WRITE", "0") == "warn" + else os.environ.get("PANDAS_COPY_ON_WRITE", "0") == "1", copy_on_write_doc, - validator=is_bool, + validator=is_one_of_factory([True, False, "warn"]), ) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index e131d689b6a40..8eb56eea3d3df 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -30,6 +30,7 @@ from pandas._config import ( config, using_copy_on_write, + warn_copy_on_write, ) from pandas._libs import lib @@ -4396,7 +4397,7 @@ def _check_setitem_copy(self, t: str = "setting", force: bool_t = False): df.iloc[0:5]['group'] = 'a' """ - if using_copy_on_write(): + if using_copy_on_write() or warn_copy_on_write(): return # return early if the check is not needed diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py index b1db2d2e708e8..13c93c5b9b9a9 100644 --- a/pandas/core/internals/managers.py +++ b/pandas/core/internals/managers.py @@ -16,7 +16,10 @@ import numpy as np -from pandas._config import using_copy_on_write +from pandas._config import ( + using_copy_on_write, + warn_copy_on_write, +) from pandas._libs import ( internals as libinternals, @@ -1988,9 +1991,17 @@ def setitem_inplace(self, indexer, value) -> None: in place, not returning a new Manager (and Block), and thus never changing the dtype. """ - if using_copy_on_write() and not self._has_no_reference(0): - self.blocks = (self._block.copy(),) - self._cache.clear() + if not self._has_no_reference(0): + if using_copy_on_write(): + self.blocks = (self._block.copy(),) + self._cache.clear() + elif warn_copy_on_write(): + warnings.warn( + "Setting value on view: behaviour will change in pandas 3.0 " + "with Copy-on-Write ...", + FutureWarning, + stacklevel=find_stack_level(), + ) super().setitem_inplace(indexer, value) diff --git a/pandas/tests/copy_view/test_indexing.py b/pandas/tests/copy_view/test_indexing.py index ebb25bd5c57d3..711d1bd26363f 100644 --- a/pandas/tests/copy_view/test_indexing.py +++ b/pandas/tests/copy_view/test_indexing.py @@ -139,7 +139,9 @@ def test_subset_row_slice(backend, using_copy_on_write): @pytest.mark.parametrize( "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"] ) -def test_subset_column_slice(backend, using_copy_on_write, using_array_manager, dtype): +def test_subset_column_slice( + backend, using_copy_on_write, warn_copy_on_write, using_array_manager, dtype +): # Case: taking a subset of the columns of a DataFrame using a slice # + afterwards modifying the subset dtype_backend, DataFrame, _ = backend @@ -159,10 +161,14 @@ def test_subset_column_slice(backend, using_copy_on_write, using_array_manager, subset.iloc[0, 0] = 0 assert not np.shares_memory(get_array(subset, "b"), get_array(df, "b")) - else: # we only get a warning in case of a single block - warn = SettingWithCopyWarning if single_block else None + # TODO + warn = ( + SettingWithCopyWarning + if (single_block and not warn_copy_on_write) + else None + ) with pd.option_context("chained_assignment", "warn"): with tm.assert_produces_warning(warn): subset.iloc[0, 0] = 0 @@ -303,7 +309,9 @@ def test_subset_iloc_rows_columns( [slice(0, 2), np.array([True, True, False]), np.array([0, 1])], ids=["slice", "mask", "array"], ) -def test_subset_set_with_row_indexer(backend, indexer_si, indexer, using_copy_on_write): +def test_subset_set_with_row_indexer( + backend, indexer_si, indexer, using_copy_on_write, warn_copy_on_write +): # Case: setting values with a row indexer on a viewing subset # subset[indexer] = value and subset.iloc[indexer] = value _, DataFrame, _ = backend @@ -318,7 +326,8 @@ def test_subset_set_with_row_indexer(backend, indexer_si, indexer, using_copy_on ): pytest.skip("setitem with labels selects on columns") - if using_copy_on_write: + # TODO + if using_copy_on_write or warn_copy_on_write: indexer_si(subset)[indexer] = 0 else: # INFO iloc no longer raises warning since pandas 1.4 @@ -340,7 +349,7 @@ def test_subset_set_with_row_indexer(backend, indexer_si, indexer, using_copy_on tm.assert_frame_equal(df, df_orig) -def test_subset_set_with_mask(backend, using_copy_on_write): +def test_subset_set_with_mask(backend, using_copy_on_write, warn_copy_on_write): # Case: setting values with a mask on a viewing subset: subset[mask] = value _, DataFrame, _ = backend df = DataFrame({"a": [1, 2, 3, 4], "b": [4, 5, 6, 7], "c": [0.1, 0.2, 0.3, 0.4]}) @@ -349,7 +358,8 @@ def test_subset_set_with_mask(backend, using_copy_on_write): mask = subset > 3 - if using_copy_on_write: + # TODO + if using_copy_on_write or warn_copy_on_write: subset[mask] = 0 else: with pd.option_context("chained_assignment", "warn"): @@ -370,7 +380,7 @@ def test_subset_set_with_mask(backend, using_copy_on_write): tm.assert_frame_equal(df, df_orig) -def test_subset_set_column(backend, using_copy_on_write): +def test_subset_set_column(backend, using_copy_on_write, warn_copy_on_write): # Case: setting a single column on a viewing subset -> subset[col] = value dtype_backend, DataFrame, _ = backend df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]}) @@ -382,7 +392,8 @@ def test_subset_set_column(backend, using_copy_on_write): else: arr = pd.array([10, 11], dtype="Int64") - if using_copy_on_write: + # TODO + if using_copy_on_write or warn_copy_on_write: subset["a"] = arr else: with pd.option_context("chained_assignment", "warn"): @@ -472,7 +483,7 @@ def test_subset_set_column_with_loc2(backend, using_copy_on_write, using_array_m @pytest.mark.parametrize( "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"] ) -def test_subset_set_columns(backend, using_copy_on_write, dtype): +def test_subset_set_columns(backend, using_copy_on_write, warn_copy_on_write, dtype): # Case: setting multiple columns on a viewing subset # -> subset[[col1, col2]] = value dtype_backend, DataFrame, _ = backend @@ -482,7 +493,8 @@ def test_subset_set_columns(backend, using_copy_on_write, dtype): df_orig = df.copy() subset = df[1:3] - if using_copy_on_write: + # TODO + if using_copy_on_write or warn_copy_on_write: subset[["a", "c"]] = 0 else: with pd.option_context("chained_assignment", "warn"): @@ -879,7 +891,9 @@ def test_del_series(backend): # Accessing column as Series -def test_column_as_series(backend, using_copy_on_write, using_array_manager): +def test_column_as_series( + backend, using_copy_on_write, warn_copy_on_write, using_array_manager +): # Case: selecting a single column now also uses Copy-on-Write dtype_backend, DataFrame, Series = backend df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]}) @@ -892,10 +906,14 @@ def test_column_as_series(backend, using_copy_on_write, using_array_manager): if using_copy_on_write or using_array_manager: s[0] = 0 else: - warn = SettingWithCopyWarning if dtype_backend == "numpy" else None - with pd.option_context("chained_assignment", "warn"): - with tm.assert_produces_warning(warn): + if warn_copy_on_write: + with tm.assert_produces_warning(FutureWarning): s[0] = 0 + else: + warn = SettingWithCopyWarning if dtype_backend == "numpy" else None + with pd.option_context("chained_assignment", "warn"): + with tm.assert_produces_warning(warn): + s[0] = 0 expected = Series([0, 2, 3], name="a") tm.assert_series_equal(s, expected) @@ -910,7 +928,7 @@ def test_column_as_series(backend, using_copy_on_write, using_array_manager): def test_column_as_series_set_with_upcast( - backend, using_copy_on_write, using_array_manager + backend, using_copy_on_write, using_array_manager, warn_copy_on_write ): # Case: selecting a single column now also uses Copy-on-Write -> when # setting a value causes an upcast, we don't need to update the parent @@ -921,10 +939,12 @@ def test_column_as_series_set_with_upcast( s = df["a"] if dtype_backend == "nullable": - with pytest.raises(TypeError, match="Invalid value"): - s[0] = "foo" + warn = FutureWarning if warn_copy_on_write else None + with tm.assert_produces_warning(warn): + with pytest.raises(TypeError, match="Invalid value"): + s[0] = "foo" expected = Series([1, 2, 3], name="a") - elif using_copy_on_write or using_array_manager: + elif using_copy_on_write or warn_copy_on_write or using_array_manager: with tm.assert_produces_warning(FutureWarning, match="incompatible dtype"): s[0] = "foo" expected = Series(["foo", 2, 3], dtype=object, name="a") @@ -962,7 +982,12 @@ def test_column_as_series_set_with_upcast( ids=["getitem", "loc", "iloc"], ) def test_column_as_series_no_item_cache( - request, backend, method, using_copy_on_write, using_array_manager + request, + backend, + method, + using_copy_on_write, + warn_copy_on_write, + using_array_manager, ): # Case: selecting a single column (which now also uses Copy-on-Write to protect # the view) should always give a new object (i.e. not make use of a cache) @@ -979,7 +1004,8 @@ def test_column_as_series_no_item_cache( else: assert s1 is s2 - if using_copy_on_write or using_array_manager: + # TODO + if using_copy_on_write or warn_copy_on_write or using_array_manager: s1.iloc[0] = 0 else: warn = SettingWithCopyWarning if dtype_backend == "numpy" else None diff --git a/pandas/tests/copy_view/test_methods.py b/pandas/tests/copy_view/test_methods.py index 76b974330cbf1..91c62d04fd435 100644 --- a/pandas/tests/copy_view/test_methods.py +++ b/pandas/tests/copy_view/test_methods.py @@ -1651,7 +1651,7 @@ def test_isetitem_frame(using_copy_on_write): @pytest.mark.parametrize("key", ["a", ["a"]]) -def test_get(using_copy_on_write, key): +def test_get(using_copy_on_write, warn_copy_on_write, key): df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) df_orig = df.copy() @@ -1665,7 +1665,12 @@ def test_get(using_copy_on_write, key): else: # for non-CoW it depends on whether we got a Series or DataFrame if it # is a view or copy or triggers a warning or not - warn = SettingWithCopyWarning if isinstance(key, list) else None + # TODO(CoW) should warn + warn = ( + (None if warn_copy_on_write else SettingWithCopyWarning) + if isinstance(key, list) + else None + ) with pd.option_context("chained_assignment", "warn"): with tm.assert_produces_warning(warn): result.iloc[0] = 0 @@ -1680,7 +1685,9 @@ def test_get(using_copy_on_write, key): @pytest.mark.parametrize( "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"] ) -def test_xs(using_copy_on_write, using_array_manager, axis, key, dtype): +def test_xs( + using_copy_on_write, warn_copy_on_write, using_array_manager, axis, key, dtype +): single_block = (dtype == "int64") and not using_array_manager is_view = single_block or (using_array_manager and axis == 1) df = DataFrame( @@ -1695,8 +1702,13 @@ def test_xs(using_copy_on_write, using_array_manager, axis, key, dtype): elif using_copy_on_write: assert result._mgr._has_no_reference(0) + # TODO(CoW) should warn in case of is_view if using_copy_on_write or is_view: result.iloc[0] = 0 + elif warn_copy_on_write: + warn = FutureWarning if single_block else None + with tm.assert_produces_warning(warn): + result.iloc[0] = 0 else: with pd.option_context("chained_assignment", "warn"): with tm.assert_produces_warning(SettingWithCopyWarning): @@ -1710,7 +1722,9 @@ def test_xs(using_copy_on_write, using_array_manager, axis, key, dtype): @pytest.mark.parametrize("axis", [0, 1]) @pytest.mark.parametrize("key, level", [("l1", 0), (2, 1)]) -def test_xs_multiindex(using_copy_on_write, using_array_manager, key, level, axis): +def test_xs_multiindex( + using_copy_on_write, warn_copy_on_write, using_array_manager, key, level, axis +): arr = np.arange(18).reshape(6, 3) index = MultiIndex.from_product([["l1", "l2"], [1, 2, 3]], names=["lev1", "lev2"]) df = DataFrame(arr, index=index, columns=list("abc")) @@ -1725,8 +1739,9 @@ def test_xs_multiindex(using_copy_on_write, using_array_manager, key, level, axi get_array(df, df.columns[0]), get_array(result, result.columns[0]) ) + # TODO(CoW) should warn warn = ( - SettingWithCopyWarning + (None if warn_copy_on_write else SettingWithCopyWarning) if not using_copy_on_write and not using_array_manager else None ) diff --git a/pandas/tests/extension/conftest.py b/pandas/tests/extension/conftest.py index a94f7de283d01..30b10541c8f66 100644 --- a/pandas/tests/extension/conftest.py +++ b/pandas/tests/extension/conftest.py @@ -215,6 +215,6 @@ def using_copy_on_write() -> bool: Fixture to check if Copy-on-Write is enabled. """ return ( - options.mode.copy_on_write + options.mode.copy_on_write is True and _get_option("mode.data_manager", silent=True) == "block" ) diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py index de8df15a9d747..ba8d713df5787 100644 --- a/pandas/tests/frame/indexing/test_indexing.py +++ b/pandas/tests/frame/indexing/test_indexing.py @@ -288,7 +288,7 @@ def test_setattr_column(self): df.foobar = 5 assert (df.foobar == 5).all() - def test_setitem(self, float_frame, using_copy_on_write): + def test_setitem(self, float_frame, using_copy_on_write, warn_copy_on_write): # not sure what else to do here series = float_frame["A"][::2] float_frame["col5"] = series @@ -324,7 +324,7 @@ def test_setitem(self, float_frame, using_copy_on_write): smaller = float_frame[:2] msg = r"\nA value is trying to be set on a copy of a slice from a DataFrame" - if using_copy_on_write: + if using_copy_on_write or warn_copy_on_write: # With CoW, adding a new column doesn't raise a warning smaller["col10"] = ["1", "2"] else: @@ -1295,7 +1295,7 @@ def test_setting_mismatched_na_into_nullable_fails( ): # GH#44514 don't cast mismatched nulls to pd.NA df = DataFrame({"A": [1, 2, 3]}, dtype=any_numeric_ea_dtype) - ser = df["A"] + ser = df["A"].copy() arr = ser._values msg = "|".join( diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index ccc1249088f9a..5076767f2e438 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -1271,7 +1271,9 @@ def test_setitem_not_operating_inplace(self, value, set_value, indexer): tm.assert_frame_equal(view, expected) @td.skip_array_manager_invalid_test - def test_setitem_column_update_inplace(self, using_copy_on_write): + def test_setitem_column_update_inplace( + self, using_copy_on_write, warn_copy_on_write + ): # https://github.com/pandas-dev/pandas/issues/47172 labels = [f"c{i}" for i in range(10)] @@ -1279,8 +1281,10 @@ def test_setitem_column_update_inplace(self, using_copy_on_write): values = df._mgr.blocks[0].values if not using_copy_on_write: - for label in df.columns: - df[label][label] = 1 + warn = FutureWarning if warn_copy_on_write else None + with tm.assert_produces_warning(warn): + for label in df.columns: + df[label][label] = 1 # diagonal values all updated assert np.all(values[np.arange(10), np.arange(10)] == 1) diff --git a/pandas/tests/frame/indexing/test_xs.py b/pandas/tests/frame/indexing/test_xs.py index 492dd387971c8..772738ae460b9 100644 --- a/pandas/tests/frame/indexing/test_xs.py +++ b/pandas/tests/frame/indexing/test_xs.py @@ -198,14 +198,17 @@ def test_xs_level_eq_2(self): tm.assert_frame_equal(result, expected) def test_xs_setting_with_copy_error( - self, multiindex_dataframe_random_data, using_copy_on_write + self, + multiindex_dataframe_random_data, + using_copy_on_write, + warn_copy_on_write, ): # this is a copy in 0.14 df = multiindex_dataframe_random_data df_orig = df.copy() result = df.xs("two", level="second") - if using_copy_on_write: + if using_copy_on_write or warn_copy_on_write: result[:] = 10 else: # setting this will give a SettingWithCopyError @@ -216,14 +219,14 @@ def test_xs_setting_with_copy_error( tm.assert_frame_equal(df, df_orig) def test_xs_setting_with_copy_error_multiple( - self, four_level_index_dataframe, using_copy_on_write + self, four_level_index_dataframe, using_copy_on_write, warn_copy_on_write ): # this is a copy in 0.14 df = four_level_index_dataframe df_orig = df.copy() result = df.xs(("a", 4), level=["one", "four"]) - if using_copy_on_write: + if using_copy_on_write or warn_copy_on_write: result[:] = 10 else: # setting this will give a SettingWithCopyError diff --git a/pandas/tests/frame/test_block_internals.py b/pandas/tests/frame/test_block_internals.py index 9e8d92e832d01..c0fb3ccbd7c79 100644 --- a/pandas/tests/frame/test_block_internals.py +++ b/pandas/tests/frame/test_block_internals.py @@ -331,7 +331,7 @@ def test_is_mixed_type(self, float_frame, float_string_frame): assert not float_frame._is_mixed_type assert float_string_frame._is_mixed_type - def test_stale_cached_series_bug_473(self, using_copy_on_write): + def test_stale_cached_series_bug_473(self, using_copy_on_write, warn_copy_on_write): # this is chained, but ok with option_context("chained_assignment", None): Y = DataFrame( @@ -344,6 +344,9 @@ def test_stale_cached_series_bug_473(self, using_copy_on_write): if using_copy_on_write: with tm.raises_chained_assignment_error(): Y["g"]["c"] = np.nan + elif warn_copy_on_write: + with tm.assert_produces_warning(FutureWarning): + Y["g"]["c"] = np.nan else: Y["g"]["c"] = np.nan repr(Y) @@ -354,13 +357,16 @@ def test_stale_cached_series_bug_473(self, using_copy_on_write): else: assert pd.isna(Y["g"]["c"]) + @pytest.mark.filterwarnings("ignore:Setting value on view:FutureWarning") def test_strange_column_corruption_issue(self, using_copy_on_write): # TODO(wesm): Unclear how exactly this is related to internal matters df = DataFrame(index=[0, 1]) df[0] = np.nan wasCol = {} - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning( + PerformanceWarning, raise_on_extra_warnings=False + ): for i, dt in enumerate(df.index): for col in range(100, 200): if col not in wasCol: diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 543a5be9544cc..d8e384477e963 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -293,16 +293,21 @@ def test_constructor_dtype_copy(self): new_df["col1"] = 200.0 assert orig_df["col1"][0] == 1.0 - def test_constructor_dtype_nocast_view_dataframe(self, using_copy_on_write): + def test_constructor_dtype_nocast_view_dataframe( + self, using_copy_on_write, warn_copy_on_write + ): df = DataFrame([[1, 2]]) should_be_view = DataFrame(df, dtype=df[0].dtype) if using_copy_on_write: should_be_view.iloc[0, 0] = 99 assert df.values[0, 0] == 1 else: - should_be_view[0][0] = 99 + warn = FutureWarning if warn_copy_on_write else None + with tm.assert_produces_warning(warn): + should_be_view[0][0] = 99 assert df.values[0, 0] == 99 + @pytest.mark.filterwarnings("ignore:Setting value on view:FutureWarning") def test_constructor_dtype_nocast_view_2d_array( self, using_array_manager, using_copy_on_write ): diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index 4ca8b0e317bd2..4eb007bbac205 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -46,7 +46,7 @@ def test_groupby_std_datetimelike(): ser = Series(tdi) ser[::5] *= 2 # get different std for different groups - df = ser.to_frame("A") + df = ser.to_frame("A").copy() df["B"] = ser + Timestamp(0) df["C"] = ser + Timestamp(0, tz="UTC") diff --git a/pandas/tests/series/accessors/test_dt_accessor.py b/pandas/tests/series/accessors/test_dt_accessor.py index fba16749c026e..823ccd0c18e9a 100644 --- a/pandas/tests/series/accessors/test_dt_accessor.py +++ b/pandas/tests/series/accessors/test_dt_accessor.py @@ -281,7 +281,7 @@ def test_dt_accessor_ambiguous_freq_conversions(self): expected = Series(exp_values, name="xxx") tm.assert_series_equal(ser, expected) - def test_dt_accessor_not_writeable(self, using_copy_on_write): + def test_dt_accessor_not_writeable(self, using_copy_on_write, warn_copy_on_write): # no setting allowed ser = Series(date_range("20130101", periods=5, freq="D"), name="xxx") with pytest.raises(ValueError, match="modifications"): @@ -293,6 +293,10 @@ def test_dt_accessor_not_writeable(self, using_copy_on_write): if using_copy_on_write: with tm.raises_chained_assignment_error(): ser.dt.hour[0] = 5 + elif warn_copy_on_write: + # TODO should warn + # with tm.assert_produces_warning(FutureWarning): + ser.dt.hour[0] = 5 else: with pytest.raises(SettingWithCopyError, match=msg): ser.dt.hour[0] = 5 diff --git a/pandas/tests/series/indexing/test_datetime.py b/pandas/tests/series/indexing/test_datetime.py index 317967bcbb7ff..6799f13688027 100644 --- a/pandas/tests/series/indexing/test_datetime.py +++ b/pandas/tests/series/indexing/test_datetime.py @@ -427,10 +427,10 @@ def test_indexing(): # getting # GH 3070, make sure semantics work on Series/Frame - expected = ts["2001"] - expected.name = "A" + result = ts["2001"] + tm.assert_series_equal(result, ts.iloc[:12]) - df = DataFrame({"A": ts}) + df = DataFrame({"A": ts.copy()}) # GH#36179 pre-2.0 df["2001"] operated as slicing on rows. in 2.0 it behaves # like any other key, so raises @@ -438,14 +438,16 @@ def test_indexing(): df["2001"] # setting + ts = Series(np.random.default_rng(2).random(len(idx)), index=idx) + expected = ts.copy() + expected.iloc[:12] = 1 ts["2001"] = 1 - expected = ts["2001"] - expected.name = "A" + tm.assert_series_equal(ts, expected) + expected = df.copy() + expected.iloc[:12, 0] = 1 df.loc["2001", "A"] = 1 - - with pytest.raises(KeyError, match="2001"): - df["2001"] + tm.assert_frame_equal(df, expected) def test_getitem_str_month_with_datetimeindex(): diff --git a/pandas/tests/series/methods/test_copy.py b/pandas/tests/series/methods/test_copy.py index 77600e0e7d293..9531f1acd78f5 100644 --- a/pandas/tests/series/methods/test_copy.py +++ b/pandas/tests/series/methods/test_copy.py @@ -38,6 +38,7 @@ def test_copy(self, deep, using_copy_on_write): assert np.isnan(ser2[0]) assert np.isnan(ser[0]) + @pytest.mark.filterwarnings("ignore:Setting value on view:FutureWarning") @pytest.mark.parametrize("deep", ["default", None, False, True]) def test_copy_tzaware(self, deep, using_copy_on_write): # GH#11794 diff --git a/pandas/tests/series/methods/test_fillna.py b/pandas/tests/series/methods/test_fillna.py index 46bc14da59eb0..5d0ef893d5723 100644 --- a/pandas/tests/series/methods/test_fillna.py +++ b/pandas/tests/series/methods/test_fillna.py @@ -248,7 +248,7 @@ def test_timedelta_fillna(self, frame_or_series): ] ) td = ser.diff() - obj = frame_or_series(td) + obj = frame_or_series(td).copy() # reg fillna result = obj.fillna(Timedelta(seconds=0)) @@ -321,7 +321,7 @@ def test_timedelta_fillna(self, frame_or_series): # ffill td[2] = np.nan - obj = frame_or_series(td) + obj = frame_or_series(td).copy() result = obj.ffill() expected = td.fillna(Timedelta(seconds=0)) expected[0] = np.nan diff --git a/pandas/tests/series/methods/test_rename.py b/pandas/tests/series/methods/test_rename.py index 93c4fbb7f3c46..a119d82711276 100644 --- a/pandas/tests/series/methods/test_rename.py +++ b/pandas/tests/series/methods/test_rename.py @@ -162,12 +162,14 @@ def test_rename_error_arg(self): with pytest.raises(KeyError, match=match): ser.rename({2: 9}, errors="raise") - def test_rename_copy_false(self, using_copy_on_write): + def test_rename_copy_false(self, using_copy_on_write, warn_copy_on_write): # GH 46889 ser = Series(["foo", "bar"]) ser_orig = ser.copy() shallow_copy = ser.rename({1: 9}, copy=False) - ser[0] = "foobar" + warn = FutureWarning if warn_copy_on_write else None + with tm.assert_produces_warning(warn): + ser[0] = "foobar" if using_copy_on_write: assert ser_orig[0] == shallow_copy[0] assert ser_orig[1] == shallow_copy[9] diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 5a05a1840b644..54c97230b761c 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -887,12 +887,14 @@ def test_constructor_invalid_coerce_ints_with_float_nan(self, any_int_numpy_dtyp with pytest.raises(IntCastingNaNError, match=msg): Series(np.array(vals), dtype=any_int_numpy_dtype) - def test_constructor_dtype_no_cast(self, using_copy_on_write): + def test_constructor_dtype_no_cast(self, using_copy_on_write, warn_copy_on_write): # see gh-1572 s = Series([1, 2, 3]) s2 = Series(s, dtype=np.int64) - s2[1] = 5 + warn = FutureWarning if warn_copy_on_write else None + with tm.assert_produces_warning(warn): + s2[1] = 5 if using_copy_on_write: assert s[1] == 2 else: diff --git a/pandas/util/_test_decorators.py b/pandas/util/_test_decorators.py index 9be0c3edaa998..a6b7dc74a0e4c 100644 --- a/pandas/util/_test_decorators.py +++ b/pandas/util/_test_decorators.py @@ -243,11 +243,11 @@ def mark_array_manager_not_yet_implemented(request) -> None: ) skip_copy_on_write_not_yet_implemented = pytest.mark.xfail( - get_option("mode.copy_on_write"), + get_option("mode.copy_on_write") is True, reason="Not yet implemented/adapted for Copy-on-Write mode", ) skip_copy_on_write_invalid_test = pytest.mark.skipif( - get_option("mode.copy_on_write"), + get_option("mode.copy_on_write") is True, reason="Test not valid for Copy-on-Write mode", )