Skip to content

Commit 09b8464

Browse files
authored
Merge branch 'main' into bug/62915-dst-union-fix
2 parents 1b25d69 + a505423 commit 09b8464

23 files changed

+134
-75
lines changed

pandas/_libs/internals.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,7 @@ class BlockValuesRefs:
9494
def add_reference(self, blk: Block) -> None: ...
9595
def add_index_reference(self, index: Index) -> None: ...
9696
def has_reference(self) -> bool: ...
97+
98+
class SetitemMixin:
99+
def __setitem__(self, key, value) -> None: ...
100+
def __delitem__(self, key) -> None: ...

pandas/_libs/internals.pyx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from collections import defaultdict
2+
import sys
3+
import warnings
24

35
cimport cython
6+
from cpython cimport PY_VERSION_HEX
47
from cpython.object cimport PyObject
58
from cpython.pyport cimport PY_SSIZE_T_MAX
69
from cpython.slice cimport PySlice_GetIndicesEx
@@ -20,6 +23,9 @@ from numpy cimport (
2023
cnp.import_array()
2124

2225
from pandas._libs.algos import ensure_int64
26+
from pandas.compat import CHAINED_WARNING_DISABLED
27+
from pandas.errors import ChainedAssignmentError
28+
from pandas.errors.cow import _chained_assignment_msg
2329

2430
from pandas._libs.util cimport (
2531
is_array,
@@ -996,3 +1002,43 @@ cdef class BlockValuesRefs:
9961002
return self._has_reference_maybe_locked()
9971003
ELSE:
9981004
return self._has_reference_maybe_locked()
1005+
1006+
1007+
cdef extern from "Python.h":
1008+
"""
1009+
#if PY_VERSION_HEX < 0x030E0000
1010+
int __Pyx_PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *ref);
1011+
#else
1012+
#define __Pyx_PyUnstable_Object_IsUniqueReferencedTemporary \
1013+
PyUnstable_Object_IsUniqueReferencedTemporary
1014+
#endif
1015+
"""
1016+
int PyUnstable_Object_IsUniqueReferencedTemporary\
1017+
"__Pyx_PyUnstable_Object_IsUniqueReferencedTemporary"(object o) except -1
1018+
1019+
1020+
# Python version compatibility for PyUnstable_Object_IsUniqueReferencedTemporary
1021+
cdef inline bint _is_unique_referenced_temporary(object obj) except -1:
1022+
if PY_VERSION_HEX >= 0x030E0000:
1023+
# Python 3.14+ has PyUnstable_Object_IsUniqueReferencedTemporary
1024+
return PyUnstable_Object_IsUniqueReferencedTemporary(obj)
1025+
else:
1026+
# Fallback for older Python versions using sys.getrefcount
1027+
return sys.getrefcount(obj) <= 1
1028+
1029+
1030+
cdef class SetitemMixin:
1031+
# class used in DataFrame and Series for checking for chained assignment
1032+
1033+
def __setitem__(self, key, value) -> None:
1034+
cdef bint is_unique = 0
1035+
if not CHAINED_WARNING_DISABLED:
1036+
is_unique = _is_unique_referenced_temporary(self)
1037+
if is_unique:
1038+
warnings.warn(
1039+
_chained_assignment_msg, ChainedAssignmentError, stacklevel=1
1040+
)
1041+
self._setitem(key, value)
1042+
1043+
def __delitem__(self, key) -> None:
1044+
self._delitem(key)

pandas/_testing/contexts.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import uuid
1414

1515
from pandas.compat import (
16-
PYPY,
17-
WARNING_CHECK_DISABLED,
16+
CHAINED_WARNING_DISABLED,
17+
CHAINED_WARNING_DISABLED_INPLACE_METHOD,
1818
)
1919
from pandas.errors import ChainedAssignmentError
2020

@@ -163,10 +163,18 @@ def with_csv_dialect(name: str, **kwargs) -> Generator[None]:
163163
csv.unregister_dialect(name)
164164

165165

166-
def raises_chained_assignment_error(extra_warnings=(), extra_match=()):
166+
def raises_chained_assignment_error(
167+
extra_warnings=(), extra_match=(), inplace_method=False
168+
):
167169
from pandas._testing import assert_produces_warning
168170

169-
if PYPY or WARNING_CHECK_DISABLED:
171+
WARNING_DISABLED = (
172+
CHAINED_WARNING_DISABLED_INPLACE_METHOD
173+
if inplace_method
174+
else CHAINED_WARNING_DISABLED
175+
)
176+
177+
if WARNING_DISABLED:
170178
if not extra_warnings:
171179
from contextlib import nullcontext
172180

pandas/compat/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
from typing import TYPE_CHECKING
1717

1818
from pandas.compat._constants import (
19+
CHAINED_WARNING_DISABLED,
20+
CHAINED_WARNING_DISABLED_INPLACE_METHOD,
1921
IS64,
2022
ISMUSL,
2123
PY312,
2224
PY314,
2325
PYPY,
24-
WARNING_CHECK_DISABLED,
2526
WASM,
2627
)
2728
from pandas.compat.numpy import is_numpy_dev
@@ -152,14 +153,15 @@ def is_ci_environment() -> bool:
152153

153154

154155
__all__ = [
156+
"CHAINED_WARNING_DISABLED",
157+
"CHAINED_WARNING_DISABLED_INPLACE_METHOD",
155158
"HAS_PYARROW",
156159
"IS64",
157160
"ISMUSL",
158161
"PY312",
159162
"PY314",
160163
"PYARROW_MIN_VERSION",
161164
"PYPY",
162-
"WARNING_CHECK_DISABLED",
163165
"WASM",
164166
"is_numpy_dev",
165167
"pa_version_under14p0",

pandas/compat/_constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
WASM = (sys.platform == "emscripten") or (platform.machine() in ["wasm32", "wasm64"])
2020
ISMUSL = "musl" in (sysconfig.get_config_var("HOST_GNU_TYPE") or "")
2121
REF_COUNT = 2
22-
WARNING_CHECK_DISABLED = PY314
22+
CHAINED_WARNING_DISABLED = PYPY or (PY314 and not sys._is_gil_enabled()) # type: ignore[attr-defined]
23+
CHAINED_WARNING_DISABLED_INPLACE_METHOD = PYPY or PY314
2324

2425

2526
__all__ = [

pandas/compat/pickle_compat.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
PeriodArray,
2323
TimedeltaArray,
2424
)
25+
from pandas.core.generic import NDFrame
2526
from pandas.core.internals import BlockManager
2627

2728
if TYPE_CHECKING:
@@ -90,6 +91,10 @@ def load_reduce(self) -> None:
9091
cls = args[0]
9192
stack[-1] = NDArrayBacked.__new__(*args)
9293
return
94+
elif args and issubclass(args[0], NDFrame):
95+
cls = args[0]
96+
stack[-1] = cls.__new__(cls)
97+
return
9398
raise
9499

95100
dispatch[pickle.REDUCE[0]] = load_reduce # type: ignore[assignment]

pandas/core/frame.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@
4848
properties,
4949
)
5050
from pandas._libs.hashtable import duplicated
51+
from pandas._libs.internals import SetitemMixin
5152
from pandas._libs.lib import is_range_indexer
52-
from pandas.compat import PYPY
5353
from pandas.compat._constants import (
54+
CHAINED_WARNING_DISABLED_INPLACE_METHOD,
5455
REF_COUNT,
55-
WARNING_CHECK_DISABLED,
5656
)
5757
from pandas.compat._optional import import_optional_dependency
5858
from pandas.compat.numpy import function as nv
@@ -63,7 +63,6 @@
6363
)
6464
from pandas.errors.cow import (
6565
_chained_assignment_method_msg,
66-
_chained_assignment_msg,
6766
)
6867
from pandas.util._decorators import (
6968
Appender,
@@ -521,7 +520,7 @@
521520

522521

523522
@set_module("pandas")
524-
class DataFrame(NDFrame, OpsMixin):
523+
class DataFrame(SetitemMixin, NDFrame, OpsMixin):
525524
"""
526525
Two-dimensional, size-mutable, potentially heterogeneous tabular data.
527526
@@ -668,6 +667,11 @@ class DataFrame(NDFrame, OpsMixin):
668667
# and ExtensionArray. Should NOT be overridden by subclasses.
669668
__pandas_priority__ = 4000
670669

670+
# override those to avoid inheriting from SetitemMixin (cython generates
671+
# them by default)
672+
__reduce__ = object.__reduce__
673+
__setstate__ = NDFrame.__setstate__
674+
671675
@property
672676
def _constructor(self) -> type[DataFrame]:
673677
return DataFrame
@@ -4321,7 +4325,8 @@ def isetitem(self, loc, value) -> None:
43214325
arraylike, refs = self._sanitize_column(value)
43224326
self._iset_item_mgr(loc, arraylike, inplace=False, refs=refs)
43234327

4324-
def __setitem__(self, key, value) -> None:
4328+
# def __setitem__() is implemented in SetitemMixin and dispatches to this method
4329+
def _setitem(self, key, value) -> None:
43254330
"""
43264331
Set item(s) in DataFrame by key.
43274332
@@ -4405,12 +4410,6 @@ def __setitem__(self, key, value) -> None:
44054410
z 3 50
44064411
# Values for 'a' and 'b' are completely ignored!
44074412
"""
4408-
if not PYPY and not WARNING_CHECK_DISABLED:
4409-
if sys.getrefcount(self) <= REF_COUNT + 1:
4410-
warnings.warn(
4411-
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
4412-
)
4413-
44144413
key = com.apply_if_callable(key, self)
44154414

44164415
# see if we can slice the rows
@@ -9363,7 +9362,7 @@ def update(
93639362
1 2 500.0
93649363
2 3 6.0
93659364
"""
9366-
if not PYPY and not WARNING_CHECK_DISABLED:
9365+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
93679366
if sys.getrefcount(self) <= REF_COUNT:
93689367
warnings.warn(
93699368
_chained_assignment_method_msg,

pandas/core/generic.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,9 @@
8282
WriteExcelBuffer,
8383
npt,
8484
)
85-
from pandas.compat import PYPY
8685
from pandas.compat._constants import (
86+
CHAINED_WARNING_DISABLED_INPLACE_METHOD,
8787
REF_COUNT,
88-
WARNING_CHECK_DISABLED,
8988
)
9089
from pandas.compat._optional import import_optional_dependency
9190
from pandas.compat.numpy import function as nv
@@ -2075,7 +2074,6 @@ def __getstate__(self) -> dict[str, Any]:
20752074
**meta,
20762075
}
20772076

2078-
@final
20792077
def __setstate__(self, state) -> None:
20802078
if isinstance(state, BlockManager):
20812079
self._mgr = state
@@ -4261,8 +4259,9 @@ def _slice(self, slobj: slice, axis: AxisInt = 0) -> Self:
42614259
result = result.__finalize__(self)
42624260
return result
42634261

4262+
# def __delitem__() is implemented in SetitemMixin and dispatches to this method
42644263
@final
4265-
def __delitem__(self, key) -> None:
4264+
def _delitem(self, key) -> None:
42664265
"""
42674266
Delete item
42684267
"""
@@ -7077,7 +7076,7 @@ def fillna(
70777076
"""
70787077
inplace = validate_bool_kwarg(inplace, "inplace")
70797078
if inplace:
7080-
if not PYPY and not WARNING_CHECK_DISABLED:
7079+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
70817080
if sys.getrefcount(self) <= REF_COUNT:
70827081
warnings.warn(
70837082
_chained_assignment_method_msg,
@@ -7324,7 +7323,7 @@ def ffill(
73247323
"""
73257324
inplace = validate_bool_kwarg(inplace, "inplace")
73267325
if inplace:
7327-
if not PYPY and not WARNING_CHECK_DISABLED:
7326+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
73287327
if sys.getrefcount(self) <= REF_COUNT:
73297328
warnings.warn(
73307329
_chained_assignment_method_msg,
@@ -7464,7 +7463,7 @@ def bfill(
74647463
"""
74657464
inplace = validate_bool_kwarg(inplace, "inplace")
74667465
if inplace:
7467-
if not PYPY and not WARNING_CHECK_DISABLED:
7466+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
74687467
if sys.getrefcount(self) <= REF_COUNT:
74697468
warnings.warn(
74707469
_chained_assignment_method_msg,
@@ -7549,7 +7548,7 @@ def replace(
75497548

75507549
inplace = validate_bool_kwarg(inplace, "inplace")
75517550
if inplace:
7552-
if not PYPY and not WARNING_CHECK_DISABLED:
7551+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
75537552
if sys.getrefcount(self) <= REF_COUNT:
75547553
warnings.warn(
75557554
_chained_assignment_method_msg,
@@ -7912,7 +7911,7 @@ def interpolate(
79127911
inplace = validate_bool_kwarg(inplace, "inplace")
79137912

79147913
if inplace:
7915-
if not PYPY and not WARNING_CHECK_DISABLED:
7914+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
79167915
if sys.getrefcount(self) <= REF_COUNT:
79177916
warnings.warn(
79187917
_chained_assignment_method_msg,
@@ -8567,7 +8566,7 @@ def clip(
85678566
inplace = validate_bool_kwarg(inplace, "inplace")
85688567

85698568
if inplace:
8570-
if not PYPY and not WARNING_CHECK_DISABLED:
8569+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
85718570
if sys.getrefcount(self) <= REF_COUNT:
85728571
warnings.warn(
85738572
_chained_assignment_method_msg,
@@ -10202,7 +10201,7 @@ def where(
1020210201
"""
1020310202
inplace = validate_bool_kwarg(inplace, "inplace")
1020410203
if inplace:
10205-
if not PYPY and not WARNING_CHECK_DISABLED:
10204+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
1020610205
if sys.getrefcount(self) <= REF_COUNT:
1020710206
warnings.warn(
1020810207
_chained_assignment_method_msg,
@@ -10266,7 +10265,7 @@ def mask(
1026610265
) -> Self | None:
1026710266
inplace = validate_bool_kwarg(inplace, "inplace")
1026810267
if inplace:
10269-
if not PYPY and not WARNING_CHECK_DISABLED:
10268+
if not CHAINED_WARNING_DISABLED_INPLACE_METHOD:
1027010269
if sys.getrefcount(self) <= REF_COUNT:
1027110270
warnings.warn(
1027210271
_chained_assignment_method_msg,

pandas/core/indexing.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515

1616
from pandas._libs.indexing import NDFrameIndexerBase
1717
from pandas._libs.lib import item_from_zerodim
18-
from pandas.compat import PYPY
1918
from pandas.compat._constants import (
19+
CHAINED_WARNING_DISABLED,
2020
REF_COUNT,
21-
WARNING_CHECK_DISABLED,
2221
)
2322
from pandas.errors import (
2423
AbstractMethodError,
@@ -920,7 +919,7 @@ def _ensure_listlike_indexer(self, key, axis=None, value=None) -> None:
920919

921920
@final
922921
def __setitem__(self, key, value) -> None:
923-
if not PYPY and not WARNING_CHECK_DISABLED:
922+
if not CHAINED_WARNING_DISABLED:
924923
if sys.getrefcount(self.obj) <= REF_COUNT:
925924
warnings.warn(
926925
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
@@ -2588,7 +2587,7 @@ def __getitem__(self, key):
25882587
return super().__getitem__(key)
25892588

25902589
def __setitem__(self, key, value) -> None:
2591-
if not PYPY and not WARNING_CHECK_DISABLED:
2590+
if not CHAINED_WARNING_DISABLED:
25922591
if sys.getrefcount(self.obj) <= REF_COUNT:
25932592
warnings.warn(
25942593
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
@@ -2619,7 +2618,7 @@ def _convert_key(self, key):
26192618
return key
26202619

26212620
def __setitem__(self, key, value) -> None:
2622-
if not PYPY and not WARNING_CHECK_DISABLED:
2621+
if not CHAINED_WARNING_DISABLED:
26232622
if sys.getrefcount(self.obj) <= REF_COUNT:
26242623
warnings.warn(
26252624
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2

0 commit comments

Comments
 (0)