diff --git a/pandas/_libs/internals.pyi b/pandas/_libs/internals.pyi index 11d059ec53920..1064995a51797 100644 --- a/pandas/_libs/internals.pyi +++ b/pandas/_libs/internals.pyi @@ -94,3 +94,7 @@ class BlockValuesRefs: def add_reference(self, blk: Block) -> None: ... def add_index_reference(self, index: Index) -> None: ... def has_reference(self) -> bool: ... + +class SetitemMixin: + def __setitem__(self, key, value) -> None: ... + def __delitem__(self, key) -> None: ... diff --git a/pandas/_libs/internals.pyx b/pandas/_libs/internals.pyx index 4fb24c9ad1538..43b60b2356b5e 100644 --- a/pandas/_libs/internals.pyx +++ b/pandas/_libs/internals.pyx @@ -1,6 +1,9 @@ from collections import defaultdict +import sys +import warnings cimport cython +from cpython cimport PY_VERSION_HEX from cpython.object cimport PyObject from cpython.pyport cimport PY_SSIZE_T_MAX from cpython.slice cimport PySlice_GetIndicesEx @@ -20,6 +23,9 @@ from numpy cimport ( cnp.import_array() from pandas._libs.algos import ensure_int64 +from pandas.compat import CHAINED_WARNING_DISABLED +from pandas.errors import ChainedAssignmentError +from pandas.errors.cow import _chained_assignment_msg from pandas._libs.util cimport ( is_array, @@ -996,3 +1002,43 @@ cdef class BlockValuesRefs: return self._has_reference_maybe_locked() ELSE: return self._has_reference_maybe_locked() + + +cdef extern from "Python.h": + """ + #if PY_VERSION_HEX < 0x030E0000 + int __Pyx_PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *ref); + #else + #define __Pyx_PyUnstable_Object_IsUniqueReferencedTemporary \ + PyUnstable_Object_IsUniqueReferencedTemporary + #endif + """ + int PyUnstable_Object_IsUniqueReferencedTemporary\ + "__Pyx_PyUnstable_Object_IsUniqueReferencedTemporary"(object o) except -1 + + +# Python version compatibility for PyUnstable_Object_IsUniqueReferencedTemporary +cdef inline bint _is_unique_referenced_temporary(object obj) except -1: + if PY_VERSION_HEX >= 0x030E0000: + # Python 3.14+ has PyUnstable_Object_IsUniqueReferencedTemporary + return PyUnstable_Object_IsUniqueReferencedTemporary(obj) + else: + # Fallback for older Python versions using sys.getrefcount + return sys.getrefcount(obj) <= 1 + + +cdef class SetitemMixin: + # class used in DataFrame and Series for checking for chained assignment + + def __setitem__(self, key, value) -> None: + cdef bint is_unique = 0 + if not CHAINED_WARNING_DISABLED: + is_unique = _is_unique_referenced_temporary(self) + if is_unique: + warnings.warn( + _chained_assignment_msg, ChainedAssignmentError, stacklevel=1 + ) + self._setitem(key, value) + + def __delitem__(self, key) -> None: + self._delitem(key) diff --git a/pandas/_testing/contexts.py b/pandas/_testing/contexts.py index ed30b2022db10..8bbd20c742c9c 100644 --- a/pandas/_testing/contexts.py +++ b/pandas/_testing/contexts.py @@ -13,8 +13,8 @@ import uuid from pandas.compat import ( - PYPY, - WARNING_CHECK_DISABLED, + CHAINED_WARNING_DISABLED, + CHAINED_WARNING_DISABLED_INPLACE_METHOD, ) from pandas.errors import ChainedAssignmentError @@ -163,10 +163,18 @@ def with_csv_dialect(name: str, **kwargs) -> Generator[None]: csv.unregister_dialect(name) -def raises_chained_assignment_error(extra_warnings=(), extra_match=()): +def raises_chained_assignment_error( + extra_warnings=(), extra_match=(), inplace_method=False +): from pandas._testing import assert_produces_warning - if PYPY or WARNING_CHECK_DISABLED: + WARNING_DISABLED = ( + CHAINED_WARNING_DISABLED_INPLACE_METHOD + if inplace_method + else CHAINED_WARNING_DISABLED + ) + + if WARNING_DISABLED: if not extra_warnings: from contextlib import nullcontext diff --git a/pandas/compat/__init__.py b/pandas/compat/__init__.py index f38abafd2db78..72d9c2555d16e 100644 --- a/pandas/compat/__init__.py +++ b/pandas/compat/__init__.py @@ -16,12 +16,13 @@ from typing import TYPE_CHECKING from pandas.compat._constants import ( + CHAINED_WARNING_DISABLED, + CHAINED_WARNING_DISABLED_INPLACE_METHOD, IS64, ISMUSL, PY312, PY314, PYPY, - WARNING_CHECK_DISABLED, WASM, ) from pandas.compat.numpy import is_numpy_dev @@ -152,6 +153,8 @@ def is_ci_environment() -> bool: __all__ = [ + "CHAINED_WARNING_DISABLED", + "CHAINED_WARNING_DISABLED_INPLACE_METHOD", "HAS_PYARROW", "IS64", "ISMUSL", @@ -159,7 +162,6 @@ def is_ci_environment() -> bool: "PY314", "PYARROW_MIN_VERSION", "PYPY", - "WARNING_CHECK_DISABLED", "WASM", "is_numpy_dev", "pa_version_under14p0", diff --git a/pandas/compat/_constants.py b/pandas/compat/_constants.py index 674afc5c62009..102f6fef6e4e1 100644 --- a/pandas/compat/_constants.py +++ b/pandas/compat/_constants.py @@ -19,7 +19,8 @@ WASM = (sys.platform == "emscripten") or (platform.machine() in ["wasm32", "wasm64"]) ISMUSL = "musl" in (sysconfig.get_config_var("HOST_GNU_TYPE") or "") REF_COUNT = 2 -WARNING_CHECK_DISABLED = PY314 +CHAINED_WARNING_DISABLED = PYPY or (PY314 and not sys._is_gil_enabled()) # type: ignore[attr-defined] +CHAINED_WARNING_DISABLED_INPLACE_METHOD = PYPY or PY314 __all__ = [ diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index beb4a69232b27..8247356f25f4d 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -22,6 +22,7 @@ PeriodArray, TimedeltaArray, ) +from pandas.core.generic import NDFrame from pandas.core.internals import BlockManager if TYPE_CHECKING: @@ -90,6 +91,10 @@ def load_reduce(self) -> None: cls = args[0] stack[-1] = NDArrayBacked.__new__(*args) return + elif args and issubclass(args[0], NDFrame): + cls = args[0] + stack[-1] = cls.__new__(cls) + return raise dispatch[pickle.REDUCE[0]] = load_reduce # type: ignore[assignment] diff --git a/pandas/core/frame.py b/pandas/core/frame.py index d4ff1bc4f35ac..c70828070a6aa 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -48,11 +48,11 @@ properties, ) from pandas._libs.hashtable import duplicated +from pandas._libs.internals import SetitemMixin from pandas._libs.lib import is_range_indexer -from pandas.compat import PYPY from pandas.compat._constants import ( + CHAINED_WARNING_DISABLED_INPLACE_METHOD, REF_COUNT, - WARNING_CHECK_DISABLED, ) from pandas.compat._optional import import_optional_dependency from pandas.compat.numpy import function as nv @@ -63,7 +63,6 @@ ) from pandas.errors.cow import ( _chained_assignment_method_msg, - _chained_assignment_msg, ) from pandas.util._decorators import ( Appender, @@ -517,7 +516,7 @@ @set_module("pandas") -class DataFrame(NDFrame, OpsMixin): +class DataFrame(SetitemMixin, NDFrame, OpsMixin): """ Two-dimensional, size-mutable, potentially heterogeneous tabular data. @@ -666,6 +665,11 @@ class DataFrame(NDFrame, OpsMixin): # and ExtensionArray. Should NOT be overridden by subclasses. __pandas_priority__ = 4000 + # override those to avoid inheriting from SetitemMixin (cython generates + # them by default) + __reduce__ = object.__reduce__ + __setstate__ = NDFrame.__setstate__ + @property def _constructor(self) -> type[DataFrame]: return DataFrame @@ -4267,7 +4271,8 @@ def isetitem(self, loc, value) -> None: arraylike, refs = self._sanitize_column(value) self._iset_item_mgr(loc, arraylike, inplace=False, refs=refs) - def __setitem__(self, key, value) -> None: + # def __setitem__() is implemented in SetitemMixin and dispatches to this method + def _setitem(self, key, value) -> None: """ Set item(s) in DataFrame by key. @@ -4351,12 +4356,6 @@ def __setitem__(self, key, value) -> None: z 3 50 # Values for 'a' and 'b' are completely ignored! """ - if not PYPY and not WARNING_CHECK_DISABLED: - if sys.getrefcount(self) <= REF_COUNT + 1: - warnings.warn( - _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 - ) - key = com.apply_if_callable(key, self) # see if we can slice the rows @@ -9314,7 +9313,7 @@ def update( 1 2 500.0 2 3 6.0 """ - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 43078ef3a263c..29d5a172ff5c5 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -82,10 +82,9 @@ WriteExcelBuffer, npt, ) -from pandas.compat import PYPY from pandas.compat._constants import ( + CHAINED_WARNING_DISABLED_INPLACE_METHOD, REF_COUNT, - WARNING_CHECK_DISABLED, ) from pandas.compat._optional import import_optional_dependency from pandas.compat.numpy import function as nv @@ -2071,7 +2070,6 @@ def __getstate__(self) -> dict[str, Any]: **meta, } - @final def __setstate__(self, state) -> None: if isinstance(state, BlockManager): self._mgr = state @@ -4267,8 +4265,9 @@ def _slice(self, slobj: slice, axis: AxisInt = 0) -> Self: result = result.__finalize__(self) return result + # def __delitem__() is implemented in SetitemMixin and dispatches to this method @final - def __delitem__(self, key) -> None: + def _delitem(self, key) -> None: """ Delete item """ @@ -7090,7 +7089,7 @@ def fillna( """ inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, @@ -7337,7 +7336,7 @@ def ffill( """ inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, @@ -7477,7 +7476,7 @@ def bfill( """ inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, @@ -7562,7 +7561,7 @@ def replace( inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, @@ -7925,7 +7924,7 @@ def interpolate( inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, @@ -8509,7 +8508,7 @@ def clip( inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, @@ -10152,7 +10151,7 @@ def where( """ inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, @@ -10216,7 +10215,7 @@ def mask( ) -> Self | None: inplace = validate_bool_kwarg(inplace, "inplace") if inplace: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 3f9749f1f7a99..27164cc3697bb 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -15,10 +15,9 @@ from pandas._libs.indexing import NDFrameIndexerBase from pandas._libs.lib import item_from_zerodim -from pandas.compat import PYPY from pandas.compat._constants import ( + CHAINED_WARNING_DISABLED, REF_COUNT, - WARNING_CHECK_DISABLED, ) from pandas.errors import ( AbstractMethodError, @@ -920,7 +919,7 @@ def _ensure_listlike_indexer(self, key, axis=None, value=None) -> None: @final def __setitem__(self, key, value) -> None: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED: if sys.getrefcount(self.obj) <= REF_COUNT: warnings.warn( _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 @@ -2588,7 +2587,7 @@ def __getitem__(self, key): return super().__getitem__(key) def __setitem__(self, key, value) -> None: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED: if sys.getrefcount(self.obj) <= REF_COUNT: warnings.warn( _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 @@ -2619,7 +2618,7 @@ def _convert_key(self, key): return key def __setitem__(self, key, value) -> None: - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED: if sys.getrefcount(self.obj) <= REF_COUNT: warnings.warn( _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 diff --git a/pandas/core/series.py b/pandas/core/series.py index f3aaee26fe470..122606465b6d9 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -33,11 +33,11 @@ properties, reshape, ) +from pandas._libs.internals import SetitemMixin from pandas._libs.lib import is_range_indexer -from pandas.compat import PYPY from pandas.compat._constants import ( + CHAINED_WARNING_DISABLED_INPLACE_METHOD, REF_COUNT, - WARNING_CHECK_DISABLED, ) from pandas.compat._optional import import_optional_dependency from pandas.compat.numpy import function as nv @@ -48,7 +48,6 @@ ) from pandas.errors.cow import ( _chained_assignment_method_msg, - _chained_assignment_msg, ) from pandas.util._decorators import ( Appender, @@ -235,7 +234,7 @@ # class "NDFrame") # definition in base class "NDFrame" @set_module("pandas") -class Series(base.IndexOpsMixin, NDFrame): # type: ignore[misc] +class Series(SetitemMixin, base.IndexOpsMixin, NDFrame): # type: ignore[misc] """ One-dimensional ndarray with axis labels (including time series). @@ -361,6 +360,11 @@ class Series(base.IndexOpsMixin, NDFrame): # type: ignore[misc] ) _mgr: SingleBlockManager + # override those to avoid inheriting from SetitemMixin (cython generates + # them by default) + __reduce__ = object.__reduce__ + __setstate__ = NDFrame.__setstate__ + # ---------------------------------------------------------------------- # Constructors @@ -1057,13 +1061,8 @@ def _get_value(self, label, takeable: bool = False): else: return self.iloc[loc] - def __setitem__(self, key, value) -> None: - if not PYPY and not WARNING_CHECK_DISABLED: - if sys.getrefcount(self) <= REF_COUNT + 1: - warnings.warn( - _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 - ) - + # def __setitem__() is implemented in SetitemMixin and dispatches to this method + def _setitem(self, key, value) -> None: check_dict_or_set_indexers(key) key = com.apply_if_callable(key, self) @@ -3327,7 +3326,7 @@ def update(self, other: Series | Sequence | Mapping) -> None: 2 3 dtype: int64 """ - if not PYPY and not WARNING_CHECK_DISABLED: + if not CHAINED_WARNING_DISABLED_INPLACE_METHOD: if sys.getrefcount(self) <= REF_COUNT: warnings.warn( _chained_assignment_method_msg, diff --git a/pandas/tests/copy_view/test_chained_assignment_deprecation.py b/pandas/tests/copy_view/test_chained_assignment_deprecation.py index f6de6af994b93..d8a75fcd380c4 100644 --- a/pandas/tests/copy_view/test_chained_assignment_deprecation.py +++ b/pandas/tests/copy_view/test_chained_assignment_deprecation.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from pandas.compat import WARNING_CHECK_DISABLED +from pandas.compat import CHAINED_WARNING_DISABLED from pandas.errors import ChainedAssignmentError from pandas import DataFrame @@ -18,7 +18,7 @@ def test_series_setitem(indexer): # using custom check instead of tm.assert_produces_warning because that doesn't # fail if multiple warnings are raised - if WARNING_CHECK_DISABLED: + if CHAINED_WARNING_DISABLED: return with pytest.warns() as record: # noqa: TID251 df["a"][indexer] = 0 diff --git a/pandas/tests/copy_view/test_clip.py b/pandas/tests/copy_view/test_clip.py index 56df33db6d416..dcc232f334a92 100644 --- a/pandas/tests/copy_view/test_clip.py +++ b/pandas/tests/copy_view/test_clip.py @@ -63,10 +63,10 @@ def test_clip_no_op(): def test_clip_chained_inplace(): df = DataFrame({"a": [1, 4, 2], "b": 1}) df_orig = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df["a"].clip(1, 2, inplace=True) tm.assert_frame_equal(df, df_orig) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df[["a"]].clip(1, 2, inplace=True) tm.assert_frame_equal(df, df_orig) diff --git a/pandas/tests/copy_view/test_interp_fillna.py b/pandas/tests/copy_view/test_interp_fillna.py index 6bcda0ef2c35a..3d2d5448e0eb2 100644 --- a/pandas/tests/copy_view/test_interp_fillna.py +++ b/pandas/tests/copy_view/test_interp_fillna.py @@ -278,11 +278,11 @@ def test_fillna_inplace_ea_noop_shares_memory(any_numeric_ea_and_arrow_dtype): def test_fillna_chained_assignment(): df = DataFrame({"a": [1, np.nan, 2], "b": 1}) df_orig = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df["a"].fillna(100, inplace=True) tm.assert_frame_equal(df, df_orig) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df[["a"]].fillna(100, inplace=True) tm.assert_frame_equal(df, df_orig) @@ -291,10 +291,10 @@ def test_fillna_chained_assignment(): def test_interpolate_chained_assignment(func): df = DataFrame({"a": [1, np.nan, 2], "b": 1}) df_orig = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): getattr(df["a"], func)(inplace=True) tm.assert_frame_equal(df, df_orig) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): getattr(df[["a"]], func)(inplace=True) tm.assert_frame_equal(df, df_orig) diff --git a/pandas/tests/copy_view/test_methods.py b/pandas/tests/copy_view/test_methods.py index 8abecd13c7038..0f0e89acee1cb 100644 --- a/pandas/tests/copy_view/test_methods.py +++ b/pandas/tests/copy_view/test_methods.py @@ -1205,11 +1205,11 @@ def test_where_mask_noop_on_single_column(dtype, val, func): def test_chained_where_mask(func): df = DataFrame({"a": [1, 4, 2], "b": 1}) df_orig = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): getattr(df["a"], func)(df["a"] > 2, 5, inplace=True) tm.assert_frame_equal(df, df_orig) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): getattr(df[["a"]], func)(df["a"] > 2, 5, inplace=True) tm.assert_frame_equal(df, df_orig) @@ -1391,11 +1391,11 @@ def test_update_chained_assignment(): df = DataFrame({"a": [1, 2, 3]}) ser2 = Series([100.0], index=[1]) df_orig = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df["a"].update(ser2) tm.assert_frame_equal(df, df_orig) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df[["a"]].update(ser2.to_frame()) tm.assert_frame_equal(df, df_orig) diff --git a/pandas/tests/copy_view/test_replace.py b/pandas/tests/copy_view/test_replace.py index d4838a5e68ab8..bbdd759128e46 100644 --- a/pandas/tests/copy_view/test_replace.py +++ b/pandas/tests/copy_view/test_replace.py @@ -319,11 +319,11 @@ def test_replace_columnwise_no_op(): def test_replace_chained_assignment(): df = DataFrame({"a": [1, np.nan, 2], "b": 1}) df_orig = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df["a"].replace(1, 100, inplace=True) tm.assert_frame_equal(df, df_orig) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df[["a"]].replace(1, 100, inplace=True) tm.assert_frame_equal(df, df_orig) diff --git a/pandas/tests/frame/methods/test_fillna.py b/pandas/tests/frame/methods/test_fillna.py index e4e6975ecd9af..d229fe5aaaa84 100644 --- a/pandas/tests/frame/methods/test_fillna.py +++ b/pandas/tests/frame/methods/test_fillna.py @@ -42,7 +42,7 @@ def test_fillna_on_column_view(self): arr = np.full((40, 50), np.nan) df = DataFrame(arr, copy=False) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df[0].fillna(-1, inplace=True) assert np.isnan(arr[:, 0]).all() diff --git a/pandas/tests/frame/methods/test_interpolate.py b/pandas/tests/frame/methods/test_interpolate.py index 25d4019fda9f8..f512ed3e4a0af 100644 --- a/pandas/tests/frame/methods/test_interpolate.py +++ b/pandas/tests/frame/methods/test_interpolate.py @@ -310,7 +310,7 @@ def test_interp_inplace(self): expected = df.copy() result = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): return_value = result["a"].interpolate(inplace=True) assert return_value is None tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/frame/test_block_internals.py b/pandas/tests/frame/test_block_internals.py index f9056a5487da6..c525a3c6494c5 100644 --- a/pandas/tests/frame/test_block_internals.py +++ b/pandas/tests/frame/test_block_internals.py @@ -378,7 +378,7 @@ def test_update_inplace_sets_valid_block_values(): df = DataFrame({"a": Series([1, 2, None], dtype="category")}) # inplace update of a single column - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df["a"].fillna(1, inplace=True) # check we haven't put a Series into any block.values diff --git a/pandas/tests/indexing/multiindex/test_chaining_and_caching.py b/pandas/tests/indexing/multiindex/test_chaining_and_caching.py index c7ed21a2cc001..7c4fbc21e7f63 100644 --- a/pandas/tests/indexing/multiindex/test_chaining_and_caching.py +++ b/pandas/tests/indexing/multiindex/test_chaining_and_caching.py @@ -28,7 +28,7 @@ def test_detect_chained_assignment(): multiind = MultiIndex.from_tuples(tuples, names=["part", "side"]) zed = DataFrame(events, index=["a", "b"], columns=multiind) - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): zed["eyes"]["right"].fillna(value=555, inplace=True) diff --git a/pandas/tests/io/test_spss.py b/pandas/tests/io/test_spss.py index 61d7225cab02e..6210c0289a160 100644 --- a/pandas/tests/io/test_spss.py +++ b/pandas/tests/io/test_spss.py @@ -13,7 +13,6 @@ # TODO(CoW) - detection of chained assignment in cython # https://github.com/pandas-dev/pandas/issues/51315 @pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError") -@pytest.mark.filterwarnings("ignore:ChainedAssignmentError:FutureWarning") @pytest.mark.parametrize("path_klass", [lambda p: p, Path]) def test_spss_labelled_num(path_klass, datapath): # test file from the Haven project (https://haven.tidyverse.org/) @@ -31,7 +30,6 @@ def test_spss_labelled_num(path_klass, datapath): @pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError") -@pytest.mark.filterwarnings("ignore:ChainedAssignmentError:FutureWarning") def test_spss_labelled_num_na(datapath): # test file from the Haven project (https://haven.tidyverse.org/) # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT @@ -48,7 +46,6 @@ def test_spss_labelled_num_na(datapath): @pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError") -@pytest.mark.filterwarnings("ignore:ChainedAssignmentError:FutureWarning") def test_spss_labelled_str(datapath): # test file from the Haven project (https://haven.tidyverse.org/) # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT @@ -65,7 +62,6 @@ def test_spss_labelled_str(datapath): @pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError") -@pytest.mark.filterwarnings("ignore:ChainedAssignmentError:FutureWarning") def test_spss_kwargs(datapath): # test file from the Haven project (https://haven.tidyverse.org/) # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT @@ -81,7 +77,6 @@ def test_spss_kwargs(datapath): @pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError") -@pytest.mark.filterwarnings("ignore:ChainedAssignmentError:FutureWarning") def test_spss_umlauts(datapath): # test file from the Haven project (https://haven.tidyverse.org/) # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT @@ -140,7 +135,6 @@ def test_invalid_dtype_backend(): @pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError") -@pytest.mark.filterwarnings("ignore:ChainedAssignmentError:FutureWarning") def test_spss_metadata(datapath): # GH 54264 fname = datapath("io", "data", "spss", "labelled-num.sav") diff --git a/pandas/tests/series/indexing/test_indexing.py b/pandas/tests/series/indexing/test_indexing.py index f2a604dcc0787..0dc5b8824958e 100644 --- a/pandas/tests/series/indexing/test_indexing.py +++ b/pandas/tests/series/indexing/test_indexing.py @@ -277,7 +277,7 @@ def test_underlying_data_conversion(): df_original = df.copy() df - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df["val"].update(s) expected = df_original tm.assert_frame_equal(df, expected) diff --git a/pandas/tests/series/methods/test_update.py b/pandas/tests/series/methods/test_update.py index 9b5fb098bf3ee..9debf67af4622 100644 --- a/pandas/tests/series/methods/test_update.py +++ b/pandas/tests/series/methods/test_update.py @@ -29,7 +29,7 @@ def test_update(self): df["c"] = df["c"].astype(object) df_orig = df.copy() - with tm.raises_chained_assignment_error(): + with tm.raises_chained_assignment_error(inplace_method=True): df["c"].update(Series(["foo"], index=[0])) expected = df_orig tm.assert_frame_equal(df, expected) diff --git a/scripts/validate_unwanted_patterns.py b/scripts/validate_unwanted_patterns.py index 04b4fde6a7c0e..80db1fc5efa6b 100755 --- a/scripts/validate_unwanted_patterns.py +++ b/scripts/validate_unwanted_patterns.py @@ -95,7 +95,10 @@ def _get_literal_string_prefix_len(token_string: str) -> int: return 0 -PRIVATE_FUNCTIONS_ALLOWED = {"sys._getframe"} # no known alternative +PRIVATE_FUNCTIONS_ALLOWED = { + "sys._getframe", + "sys._is_gil_enabled", +} # no known alternative def private_function_across_module(file_obj: IO[str]) -> Iterable[tuple[int, str]]: