.. versionadded:: 3.14
+.. c:function:: int PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *obj)
+
+ Check if *obj* is a unique temporary object.
+ Returns ``1`` if *obj* is known to be a unique temporary object,
+ and ``0`` otherwise. This function cannot fail, but the check is
+ conservative, and may return ``0`` in some cases even if *obj* is a unique
+ temporary object.
+
+ If an object is a unique temporary, it is guaranteed that the current code
+ has the only reference to the object. For arguments to C functions, this
+ should be used instead of checking if the reference count is ``1``. Starting
+ with Python 3.14, the interpreter internally avoids some reference count
+ modifications when loading objects onto the operands stack by
+ :term:`borrowing <borrowed reference>` references when possible, which means
+ that a reference count of ``1`` by itself does not guarantee that a function
+ argument uniquely referenced.
+
+ In the example below, ``my_func`` is called with a unique temporary object
+ as its argument::
+
+ my_func([1, 2, 3])
+
+ In the example below, ``my_func`` is **not** called with a unique temporary
+ object as its argument, even if its refcount is ``1``::
+
+ my_list = [1, 2, 3]
+ my_func(my_list)
+
+ See also the function :c:func:`Py_REFCNT`.
+
+ .. versionadded:: 3.14
+
.. c:function:: int PyUnstable_IsImmortal(PyObject *obj)
This function returns non-zero if *obj* is :term:`immortal`, and zero
Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count.
+ See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
+
.. versionchanged:: 3.10
:c:func:`Py_REFCNT()` is changed to the inline static function.
:mod:`multiprocessing` or :mod:`concurrent.futures`, see the
:ref:`forkserver restrictions <multiprocessing-programming-forkserver>`.
+The interpreter avoids some reference count modifications internally when
+it's safe to do so. This can lead to different values returned from
+:func:`sys.getrefcount` and :c:func:`Py_REFCNT` compared to previous versions
+of Python. See :ref:`below <whatsnew314-refcount>` for details.
New features
============
take a C integer and produce a Python :class:`bool` object. (Contributed by
Pablo Galindo in :issue:`45325`.)
+* Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` to determine if an object
+ is a unique temporary object on the interpreter's operand stack. This can
+ be used in some cases as a replacement for checking if :c:func:`Py_REFCNT`
+ is ``1`` for Python objects passed as arguments to C API functions.
+
Limited C API changes
---------------------
a :exc:`UnicodeError` object.
(Contributed by Bénédikt Tran in :gh:`127691`.)
+.. _whatsnew314-refcount:
+
+* The interpreter internally avoids some reference count modifications when
+ loading objects onto the operands stack by :term:`borrowing <borrowed reference>`
+ references when possible. This can lead to smaller reference count values
+ compared to previous Python versions. C API extensions that checked
+ :c:func:`Py_REFCNT` of ``1`` to determine if an function argument is not
+ referenced by any other code should instead use
+ :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` as a safer replacement.
+
+
* Private functions promoted to public C APIs:
* ``_PyBytes_Join()``: :c:func:`PyBytes_Join`.
*/
PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *);
+/* Determine if the object exists as a unique temporary variable on the
+ * topmost frame of the interpreter.
+ */
+PyAPI_FUNC(int) PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *);
+
/* Check whether the object is immortal. This cannot fail. */
PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *);
import enum
+import sys
import textwrap
import unittest
from test import support
obj = MyObj()
_testinternalcapi.incref_decref_delayed(obj)
+ def test_is_unique_temporary(self):
+ self.assertTrue(_testcapi.pyobject_is_unique_temporary(object()))
+ obj = object()
+ self.assertFalse(_testcapi.pyobject_is_unique_temporary(obj))
+
+ def func(x):
+ # This relies on the LOAD_FAST_BORROW optimization (gh-130704)
+ self.assertEqual(sys.getrefcount(x), 1)
+ self.assertFalse(_testcapi.pyobject_is_unique_temporary(x))
+
+ func(object())
+
if __name__ == "__main__":
unittest.main()
--- /dev/null
+Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` function for
+determining if an object exists as a unique temporary variable on the
+interpreter's stack. This is a replacement for some cases where checking
+that :c:func:`Py_REFCNT` is one is no longer sufficient to determine if it's
+safe to modify a Python object in-place with no visible side effects.
return PyLong_FromLong(result);
}
+static PyObject *
+pyobject_is_unique_temporary(PyObject *self, PyObject *obj)
+{
+ int result = PyUnstable_Object_IsUniqueReferencedTemporary(obj);
+ return PyLong_FromLong(result);
+}
+
static int MyObject_dealloc_called = 0;
static void
{"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
{"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O},
{"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O},
+ {"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O},
{"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS},
{"test_xincref_doesnt_leak",test_xincref_doesnt_leak, METH_NOARGS},
{"test_incref_doesnt_leak", test_incref_doesnt_leak, METH_NOARGS},
#include "pycore_hamt.h" // _PyHamtItems_Type
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_instruction_sequence.h" // _PyInstructionSequence_Type
+#include "pycore_interpframe.h" // _PyFrame_Stackbase()
#include "pycore_interpolation.h" // _PyInterpolation_Type
#include "pycore_list.h" // _PyList_DebugMallocStats()
#include "pycore_long.h" // _PyLong_GetZero()
#endif
}
+int
+PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *op)
+{
+ if (!_PyObject_IsUniquelyReferenced(op)) {
+ return 0;
+ }
+
+ _PyInterpreterFrame *frame = _PyEval_GetFrame();
+ if (frame == NULL) {
+ return 0;
+ }
+
+ _PyStackRef *base = _PyFrame_Stackbase(frame);
+ _PyStackRef *stackpointer = frame->stackpointer;
+ while (stackpointer > base) {
+ stackpointer--;
+ if (op == PyStackRef_AsPyObjectBorrow(*stackpointer)) {
+ return PyStackRef_IsHeapSafe(*stackpointer);
+ }
+ }
+ return 0;
+}
+
int
PyUnstable_TryIncRef(PyObject *op)
{