Skip to content

Commit d66ab22

Browse files
Test chained assignment detection for Python 3.14
1 parent 4257ad6 commit d66ab22

File tree

5 files changed

+74
-17
lines changed

5 files changed

+74
-17
lines changed

pandas/_libs/internals.pyx

Lines changed: 56 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,7 @@ from numpy cimport (
2023
cnp.import_array()
2124

2225
from pandas._libs.algos import ensure_int64
26+
from pandas.errors import ChainedAssignmentError
2327

2428
from pandas._libs.util cimport (
2529
is_array,
@@ -996,3 +1000,55 @@ cdef class BlockValuesRefs:
9961000
return self._has_reference_maybe_locked()
9971001
ELSE:
9981002
return self._has_reference_maybe_locked()
1003+
1004+
1005+
cdef extern from "Python.h":
1006+
"""
1007+
#if PY_VERSION_HEX < 0x030E0000
1008+
int __Pyx_PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *ref)
1009+
{
1010+
return 0;
1011+
}
1012+
#else
1013+
#define __Pyx_PyUnstable_Object_IsUniqueReferencedTemporary \
1014+
PyUnstable_Object_IsUniqueReferencedTemporary
1015+
#endif
1016+
"""
1017+
int PyUnstable_Object_IsUniqueReferencedTemporary\
1018+
"__Pyx_PyUnstable_Object_IsUniqueReferencedTemporary"(object o) except -1
1019+
1020+
1021+
cdef inline bint _is_unique_referenced_temporary(object obj) except -1:
1022+
if PY_VERSION_HEX >= 0x030E0000:
1023+
return PyUnstable_Object_IsUniqueReferencedTemporary(obj)
1024+
else:
1025+
return sys.getrefcount(obj) == 2
1026+
1027+
1028+
# # Python version compatibility for PyUnstable_Object_IsUniqueReferencedTemporary
1029+
# IF PY_VERSION_HEX >= 0x030E0000:
1030+
# # Python 3.14+ has PyUnstable_Object_IsUniqueReferencedTemporary
1031+
# cdef inline bint _is_unique_referenced_temporary(object obj) except -1:
1032+
# return PyUnstable_Object_IsUniqueReferencedTemporary(obj)
1033+
# ELSE:
1034+
# # Fallback for older Python versions using sys.getrefcount
1035+
# cdef inline bint _is_unique_referenced_temporary(object obj) except -1:
1036+
# # sys.getrefcount includes the reference from getrefcount itself
1037+
# # So if refcount is 2, it means only one external reference exists
1038+
# return sys.getrefcount(obj) == 2
1039+
1040+
1041+
cdef class SetitemMixin:
1042+
1043+
def __setitem__(self, key, value):
1044+
cdef bint is_unique = _is_unique_referenced_temporary(self)
1045+
# print("Refcount self: ", sys.getrefcount(self))
1046+
# print("Is unique referenced temporary: ", is_unique)
1047+
if is_unique:
1048+
warnings.warn(
1049+
"A value is trying to be set on a copy of a DataFrame or Series "
1050+
"through chained assignment.",
1051+
ChainedAssignmentError,
1052+
stacklevel=1,
1053+
)
1054+
self._setitem(key, value)

pandas/_libs/tslib.pyx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ cpdef array_to_datetime(
267267
str unit_for_numerics=None,
268268
):
269269
"""
270+
TODO no longer up to date
270271
Converts a 1D array of date-like values to a numpy array of either:
271272
1) datetime64[ns] data
272273
2) datetime.datetime objects, if OutOfBoundsDatetime or TypeError

pandas/core/frame.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
properties,
4848
)
4949
from pandas._libs.hashtable import duplicated
50+
from pandas._libs.internals import SetitemMixin
5051
from pandas._libs.lib import is_range_indexer
5152
from pandas.compat import PYPY
5253
from pandas.compat._constants import REF_COUNT
@@ -58,7 +59,6 @@
5859
)
5960
from pandas.errors.cow import (
6061
_chained_assignment_method_msg,
61-
_chained_assignment_msg,
6262
)
6363
from pandas.util._decorators import (
6464
Appender,
@@ -511,7 +511,7 @@
511511

512512

513513
@set_module("pandas")
514-
class DataFrame(NDFrame, OpsMixin):
514+
class DataFrame(NDFrame, OpsMixin, SetitemMixin):
515515
"""
516516
Two-dimensional, size-mutable, potentially heterogeneous tabular data.
517517
@@ -4212,7 +4212,7 @@ def isetitem(self, loc, value) -> None:
42124212
arraylike, refs = self._sanitize_column(value)
42134213
self._iset_item_mgr(loc, arraylike, inplace=False, refs=refs)
42144214

4215-
def __setitem__(self, key, value) -> None:
4215+
def _setitem(self, key, value) -> None:
42164216
"""
42174217
Set item(s) in DataFrame by key.
42184218
@@ -4296,11 +4296,11 @@ def __setitem__(self, key, value) -> None:
42964296
z 3 50
42974297
# Values for 'a' and 'b' are completely ignored!
42984298
"""
4299-
if not PYPY:
4300-
if sys.getrefcount(self) <= 3:
4301-
warnings.warn(
4302-
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
4303-
)
4299+
# if not PYPY:
4300+
# if sys.getrefcount(self) <= 3:
4301+
# warnings.warn(
4302+
# _chained_assignment_msg, ChainedAssignmentError, stacklevel=2
4303+
# )
43044304

43054305
key = com.apply_if_callable(key, self)
43064306

pandas/core/generic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ def _from_mgr(cls, mgr: Manager, axes: list[Index]) -> Self:
305305
The axes must match mgr.axes, but are required for future-proofing
306306
in the event that axes are refactored out of the Manager objects.
307307
"""
308-
obj = cls.__new__(cls)
308+
obj = object.__new__(cls)
309309
NDFrame.__init__(obj, mgr)
310310
return obj
311311

pandas/core/series.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
properties,
3333
reshape,
3434
)
35+
from pandas._libs.internals import SetitemMixin
3536
from pandas._libs.lib import is_range_indexer
3637
from pandas.compat import PYPY
3738
from pandas.compat._constants import REF_COUNT
@@ -43,7 +44,6 @@
4344
)
4445
from pandas.errors.cow import (
4546
_chained_assignment_method_msg,
46-
_chained_assignment_msg,
4747
)
4848
from pandas.util._decorators import (
4949
Appender,
@@ -234,7 +234,7 @@
234234
# class "NDFrame")
235235
# definition in base class "NDFrame"
236236
@set_module("pandas")
237-
class Series(base.IndexOpsMixin, NDFrame): # type: ignore[misc]
237+
class Series(base.IndexOpsMixin, NDFrame, SetitemMixin): # type: ignore[misc]
238238
"""
239239
One-dimensional ndarray with axis labels (including time series).
240240
@@ -1058,12 +1058,12 @@ def _get_value(self, label, takeable: bool = False):
10581058
else:
10591059
return self.iloc[loc]
10601060

1061-
def __setitem__(self, key, value) -> None:
1062-
if not PYPY:
1063-
if sys.getrefcount(self) <= 3:
1064-
warnings.warn(
1065-
_chained_assignment_msg, ChainedAssignmentError, stacklevel=2
1066-
)
1061+
def _setitem(self, key, value) -> None:
1062+
# if not PYPY:
1063+
# if sys.getrefcount(self) <= 3:
1064+
# warnings.warn(
1065+
# _chained_assignment_msg, ChainedAssignmentError, stacklevel=2
1066+
# )
10671067

10681068
check_dict_or_set_indexers(key)
10691069
key = com.apply_if_callable(key, self)

0 commit comments

Comments
 (0)