GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-133164: Add `PyUnstable_Object_IsUniqueReferencedTemporary` C API … · python/cpython@f237953

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • Doc
    • c-api
      • object.rst
      • refcounting.rst
    • whatsnew
      • 3.14.rst
  • Include/cpython
    • object.h
  • Lib/test/test_capi
    • test_object.py
  • Misc/NEWS.d/next/C_API
    • 2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst
  • Modules/_testcapi
    • object.c
  • Objects
    • object.c
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎Doc/c-api/object.rst‎

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 numberDiff line numberDiff line change
@@ -613,6 +613,38 @@ Object Protocol
613613
614614
.. versionadded:: 3.14
615615
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+
616648
.. c:function:: int PyUnstable_IsImmortal(PyObject *obj)
617649
618650
This function returns non-zero if *obj* is :term:`immortal`, and zero
Collapse file

‎Doc/c-api/refcounting.rst‎

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 numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ of Python objects.
2323
2424
Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count.
2525
26+
See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
27+
2628
.. versionchanged:: 3.10
2729
:c:func:`Py_REFCNT()` is changed to the inline static function.
2830
Collapse file

‎Doc/whatsnew/3.14.rst‎

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 numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ If you encounter :exc:`NameError`\s or pickling errors coming out of
8989
:mod:`multiprocessing` or :mod:`concurrent.futures`, see the
9090
:ref:`forkserver restrictions <multiprocessing-programming-forkserver>`.
9191

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.
9296

9397
New features
9498
============
@@ -2215,6 +2219,11 @@ New features
22152219
take a C integer and produce a Python :class:`bool` object. (Contributed by
22162220
Pablo Galindo in :issue:`45325`.)
22172221

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+
22182227

22192228
Limited C API changes
22202229
---------------------
@@ -2249,6 +2258,17 @@ Porting to Python 3.14
22492258
a :exc:`UnicodeError` object.
22502259
(Contributed by Bénédikt Tran in :gh:`127691`.)
22512260

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+
22522272
* Private functions promoted to public C APIs:
22532273

22542274
* ``_PyBytes_Join()``: :c:func:`PyBytes_Join`.
Collapse file

‎Include/cpython/object.h‎

Copy file name to clipboardExpand all lines: Include/cpython/object.h
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,11 @@ PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**);
476476
*/
477477
PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *);
478478

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+
479484
/* Check whether the object is immortal. This cannot fail. */
480485
PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *);
481486

Collapse file

‎Lib/test/test_capi/test_object.py‎

Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_object.py
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import enum
2+
import sys
23
import textwrap
34
import unittest
45
from test import support
@@ -223,5 +224,17 @@ def __del__(self):
223224
obj = MyObj()
224225
_testinternalcapi.incref_decref_delayed(obj)
225226

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+
226239
if __name__ == "__main__":
227240
unittest.main()
Collapse file

‎Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst‎

Copy file name to clipboard
+5Lines changed: 5 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
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

‎Modules/_testcapi/object.c‎

Copy file name to clipboardExpand all lines: Modules/_testcapi/object.c
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj)
131131
return PyLong_FromLong(result);
132132
}
133133

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+
134141
static int MyObject_dealloc_called = 0;
135142

136143
static void
@@ -478,6 +485,7 @@ static PyMethodDef test_methods[] = {
478485
{"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
479486
{"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O},
480487
{"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O},
488+
{"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O},
481489
{"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS},
482490
{"test_xincref_doesnt_leak",test_xincref_doesnt_leak, METH_NOARGS},
483491
{"test_incref_doesnt_leak", test_incref_doesnt_leak, METH_NOARGS},
Collapse file

‎Objects/object.c‎

Copy file name to clipboardExpand all lines: Objects/object.c
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "pycore_hamt.h" // _PyHamtItems_Type
1616
#include "pycore_initconfig.h" // _PyStatus_OK()
1717
#include "pycore_instruction_sequence.h" // _PyInstructionSequence_Type
18+
#include "pycore_interpframe.h" // _PyFrame_Stackbase()
1819
#include "pycore_interpolation.h" // _PyInterpolation_Type
1920
#include "pycore_list.h" // _PyList_DebugMallocStats()
2021
#include "pycore_long.h" // _PyLong_GetZero()
@@ -2621,6 +2622,29 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op)
26212622
#endif
26222623
}
26232624

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+
26242648
int
26252649
PyUnstable_TryIncRef(PyObject *op)
26262650
{

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.