GeezerVison is showing github.com — gh-133164: Add `PyUnstable_Object_IsUniqueReferencedTemporary` C API … · python/cpython@f237953
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Doc/c-api/object.rst +32Lines changed: 32 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -613,6 +613,38 @@ Object Protocol | 613 | 613 |
| 614 | 614 | .. versionadded:: 3.14 | 615 | 615 |
| | 616 | +.. c:function:: int PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *obj) | | 617 | +
| | 618 | + Check if *obj* is a unique temporary object. | | 619 | + Returns ``1`` if *obj* is known to be a unique temporary object, | | 620 | + and ``0`` otherwise. This function cannot fail, but the check is | | 621 | + conservative, and may return ``0`` in some cases even if *obj* is a unique | | 622 | + temporary object. | | 623 | +
| | 624 | + If an object is a unique temporary, it is guaranteed that the current code | | 625 | + has the only reference to the object. For arguments to C functions, this | | 626 | + should be used instead of checking if the reference count is ``1``. Starting | | 627 | + with Python 3.14, the interpreter internally avoids some reference count | | 628 | + modifications when loading objects onto the operands stack by | | 629 | + :term:`borrowing <borrowed reference>` references when possible, which means | | 630 | + that a reference count of ``1`` by itself does not guarantee that a function | | 631 | + argument uniquely referenced. | | 632 | +
| | 633 | + In the example below, ``my_func`` is called with a unique temporary object | | 634 | + as its argument:: | | 635 | +
| | 636 | + my_func([1, 2, 3]) | | 637 | +
| | 638 | + In the example below, ``my_func`` is **not** called with a unique temporary | | 639 | + object as its argument, even if its refcount is ``1``:: | | 640 | +
| | 641 | + my_list = [1, 2, 3] | | 642 | + my_func(my_list) | | 643 | +
| | 644 | + See also the function :c:func:`Py_REFCNT`. | | 645 | +
| | 646 | + .. versionadded:: 3.14 | | 647 | +
| 616 | 648 | .. c:function:: int PyUnstable_IsImmortal(PyObject *obj) | 617 | 649 |
| 618 | 650 | This function returns non-zero if *obj* is :term:`immortal`, and zero | |
Collapse file Copy file name to clipboardExpand all lines: Doc/c-api/refcounting.rst +2Lines changed: 2 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -23,6 +23,8 @@ of Python objects. | 23 | 23 |
| 24 | 24 | Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count. | 25 | 25 |
| | 26 | + See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`. | | 27 | +
| 26 | 28 | .. versionchanged:: 3.10 | 27 | 29 | :c:func:`Py_REFCNT()` is changed to the inline static function. | 28 | 30 |
| |
Collapse file Copy file name to clipboardExpand all lines: Doc/whatsnew/3.14.rst +20Lines changed: 20 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -89,6 +89,10 @@ If you encounter :exc:`NameError`\s or pickling errors coming out of | 89 | 89 | :mod:`multiprocessing` or :mod:`concurrent.futures`, see the | 90 | 90 | :ref:`forkserver restrictions <multiprocessing-programming-forkserver>`. | 91 | 91 |
| | 92 | +The interpreter avoids some reference count modifications internally when | | 93 | +it's safe to do so. This can lead to different values returned from | | 94 | +:func:`sys.getrefcount` and :c:func:`Py_REFCNT` compared to previous versions | | 95 | +of Python. See :ref:`below <whatsnew314-refcount>` for details. | 92 | 96 |
| 93 | 97 | New features | 94 | 98 | ============ | @@ -2215,6 +2219,11 @@ New features | 2215 | 2219 | take a C integer and produce a Python :class:`bool` object. (Contributed by | 2216 | 2220 | Pablo Galindo in :issue:`45325`.) | 2217 | 2221 |
| | 2222 | +* Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` to determine if an object | | 2223 | + is a unique temporary object on the interpreter's operand stack. This can | | 2224 | + be used in some cases as a replacement for checking if :c:func:`Py_REFCNT` | | 2225 | + is ``1`` for Python objects passed as arguments to C API functions. | | 2226 | + | 2218 | 2227 |
| 2219 | 2228 | Limited C API changes | 2220 | 2229 | --------------------- | @@ -2249,6 +2258,17 @@ Porting to Python 3.14 | 2249 | 2258 | a :exc:`UnicodeError` object. | 2250 | 2259 | (Contributed by Bénédikt Tran in :gh:`127691`.) | 2251 | 2260 |
| | 2261 | +.. _whatsnew314-refcount: | | 2262 | + | | 2263 | +* The interpreter internally avoids some reference count modifications when | | 2264 | + loading objects onto the operands stack by :term:`borrowing <borrowed reference>` | | 2265 | + references when possible. This can lead to smaller reference count values | | 2266 | + compared to previous Python versions. C API extensions that checked | | 2267 | + :c:func:`Py_REFCNT` of ``1`` to determine if an function argument is not | | 2268 | + referenced by any other code should instead use | | 2269 | + :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` as a safer replacement. | | 2270 | + | | 2271 | + | 2252 | 2272 | * Private functions promoted to public C APIs: | 2253 | 2273 |
| 2254 | 2274 | * ``_PyBytes_Join()``: :c:func:`PyBytes_Join`. | |
Collapse file Copy file name to clipboardExpand all lines: Include/cpython/object.h +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -476,6 +476,11 @@ PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**); | 476 | 476 | */ | 477 | 477 | PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *); | 478 | 478 |
| | 479 | +/* Determine if the object exists as a unique temporary variable on the | | 480 | + * topmost frame of the interpreter. | | 481 | + */ | | 482 | +PyAPI_FUNC(int) PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *); | | 483 | + | 479 | 484 | /* Check whether the object is immortal. This cannot fail. */ | 480 | 485 | PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *); | 481 | 486 |
| |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_object.py +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | import enum | | 2 | +import sys | 2 | 3 | import textwrap | 3 | 4 | import unittest | 4 | 5 | from test import support | @@ -223,5 +224,17 @@ def __del__(self): | 223 | 224 | obj = MyObj() | 224 | 225 | _testinternalcapi.incref_decref_delayed(obj) | 225 | 226 |
| | 227 | + def test_is_unique_temporary(self): | | 228 | + self.assertTrue(_testcapi.pyobject_is_unique_temporary(object())) | | 229 | + obj = object() | | 230 | + self.assertFalse(_testcapi.pyobject_is_unique_temporary(obj)) | | 231 | + | | 232 | + def func(x): | | 233 | + # This relies on the LOAD_FAST_BORROW optimization (gh-130704) | | 234 | + self.assertEqual(sys.getrefcount(x), 1) | | 235 | + self.assertFalse(_testcapi.pyobject_is_unique_temporary(x)) | | 236 | + | | 237 | + func(object()) | | 238 | + | 226 | 239 | if __name__ == "__main__": | 227 | 240 | unittest.main() |
Collapse file Copy file name to clipboard +5Lines changed: 5 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` function for | | 2 | +determining if an object exists as a unique temporary variable on the | | 3 | +interpreter's stack. This is a replacement for some cases where checking | | 4 | +that :c:func:`Py_REFCNT` is one is no longer sufficient to determine if it's | | 5 | +safe to modify a Python object in-place with no visible side effects. |
Collapse file Copy file name to clipboardExpand all lines: Modules/_testcapi/object.c +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -131,6 +131,13 @@ pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj) | 131 | 131 | return PyLong_FromLong(result); | 132 | 132 | } | 133 | 133 |
| | 134 | +static PyObject * | | 135 | +pyobject_is_unique_temporary(PyObject *self, PyObject *obj) | | 136 | +{ | | 137 | + int result = PyUnstable_Object_IsUniqueReferencedTemporary(obj); | | 138 | + return PyLong_FromLong(result); | | 139 | +} | | 140 | + | 134 | 141 | static int MyObject_dealloc_called = 0; | 135 | 142 |
| 136 | 143 | static void | @@ -478,6 +485,7 @@ static PyMethodDef test_methods[] = { | 478 | 485 | {"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS}, | 479 | 486 | {"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O}, | 480 | 487 | {"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O}, | | 488 | + {"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O}, | 481 | 489 | {"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS}, | 482 | 490 | {"test_xincref_doesnt_leak",test_xincref_doesnt_leak, METH_NOARGS}, | 483 | 491 | {"test_incref_doesnt_leak", test_incref_doesnt_leak, METH_NOARGS}, | |
Collapse file Copy file name to clipboardExpand all lines: Objects/object.c +24Lines changed: 24 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 15 | 15 | #include "pycore_hamt.h" // _PyHamtItems_Type | 16 | 16 | #include "pycore_initconfig.h" // _PyStatus_OK() | 17 | 17 | #include "pycore_instruction_sequence.h" // _PyInstructionSequence_Type | | 18 | +#include "pycore_interpframe.h" // _PyFrame_Stackbase() | 18 | 19 | #include "pycore_interpolation.h" // _PyInterpolation_Type | 19 | 20 | #include "pycore_list.h" // _PyList_DebugMallocStats() | 20 | 21 | #include "pycore_long.h" // _PyLong_GetZero() | @@ -2621,6 +2622,29 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) | 2621 | 2622 | #endif | 2622 | 2623 | } | 2623 | 2624 |
| | 2625 | +int | | 2626 | +PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *op) | | 2627 | +{ | | 2628 | + if (!_PyObject_IsUniquelyReferenced(op)) { | | 2629 | + return 0; | | 2630 | + } | | 2631 | + | | 2632 | + _PyInterpreterFrame *frame = _PyEval_GetFrame(); | | 2633 | + if (frame == NULL) { | | 2634 | + return 0; | | 2635 | + } | | 2636 | + | | 2637 | + _PyStackRef *base = _PyFrame_Stackbase(frame); | | 2638 | + _PyStackRef *stackpointer = frame->stackpointer; | | 2639 | + while (stackpointer > base) { | | 2640 | + stackpointer--; | | 2641 | + if (op == PyStackRef_AsPyObjectBorrow(*stackpointer)) { | | 2642 | + return PyStackRef_IsHeapSafe(*stackpointer); | | 2643 | + } | | 2644 | + } | | 2645 | + return 0; | | 2646 | +} | | 2647 | + | 2624 | 2648 | int | 2625 | 2649 | PyUnstable_TryIncRef(PyObject *op) | 2626 | 2650 | { | |
|