https://github.com/python/cpython/commit/478a315b7a22bd09b97431697d0832879631f7f1
commit: 478a315b7a22bd09b97431697d0832879631f7f1
branch: main
author: Sergey Miryanov <[email protected]>
committer: vstinner <[email protected]>
date: 2026-03-10T11:44:20+01:00
summary:

GH-145247: Implement _PyTuple_FromPair() (#145325)

Implement _PyTuple_FromPair() and _PyTuple_FromPairSteal().

Co-authored-by: Pieter Eendebak <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>
Co-authored-by: Bartosz SÅ‚awecki <[email protected]>
Co-authored-by: Kumar Aditya <[email protected]>

files:
A Modules/_testinternalcapi/tuple.c
M Include/internal/pycore_tuple.h
M Lib/test/test_capi/test_tuple.py
M Modules/Setup.stdlib.in
M Modules/_testinternalcapi.c
M Modules/_testinternalcapi/parts.h
M Objects/tupleobject.c
M PCbuild/_testinternalcapi.vcxproj
M PCbuild/_testinternalcapi.vcxproj.filters

diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h
index 00562bef769920..b3fa28f5bf21d5 100644
--- a/Include/internal/pycore_tuple.h
+++ b/Include/internal/pycore_tuple.h
@@ -27,6 +27,9 @@ PyAPI_FUNC(PyObject 
*)_PyTuple_FromStackRefStealOnSuccess(const union _PyStackRe
 PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t);
 PyAPI_FUNC(PyObject *) _PyTuple_BinarySlice(PyObject *, PyObject *, PyObject 
*);
 
+PyAPI_FUNC(PyObject *) _PyTuple_FromPair(PyObject *, PyObject *);
+PyAPI_FUNC(PyObject *) _PyTuple_FromPairSteal(PyObject *, PyObject *);
+
 typedef struct {
     PyObject_HEAD
     Py_ssize_t it_index;
diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py
index d6669d7802c5b8..0c27e81168ff77 100644
--- a/Lib/test/test_capi/test_tuple.py
+++ b/Lib/test/test_capi/test_tuple.py
@@ -1,9 +1,11 @@
 import unittest
 import gc
+from sys import getrefcount
 from test.support import import_helper
 
 _testcapi = import_helper.import_module('_testcapi')
 _testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+_testinternalcapi = import_helper.import_module('_testinternalcapi')
 
 NULL = None
 PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN
@@ -118,6 +120,41 @@ def test_tuple_pack(self):
         # CRASHES pack(1, NULL)
         # CRASHES pack(2, [1])
 
+    def check_tuple_from_pair(self, from_pair):
+        self.assertEqual(type(from_pair(1, 2)), tuple)
+        self.assertEqual(from_pair(1, 145325), (1, 145325))
+        self.assertEqual(from_pair(None, None), (None, None))
+        self.assertEqual(from_pair(True, False), (True, False))
+
+        # user class supports gc
+        class Temp:
+            pass
+        temp = Temp()
+        temp_rc = getrefcount(temp)
+        self.assertEqual(from_pair(temp, temp), (temp, temp))
+        self.assertEqual(getrefcount(temp), temp_rc)
+
+        self._not_tracked(from_pair(1, 2))
+        self._not_tracked(from_pair(None, None))
+        self._not_tracked(from_pair(True, False))
+        self._tracked(from_pair(temp, (1, 2)))
+        self._tracked(from_pair(temp, 1))
+        self._tracked(from_pair([], {}))
+
+        self.assertRaises(TypeError, from_pair, 1, 2, 3)
+        self.assertRaises(TypeError, from_pair, 1)
+        self.assertRaises(TypeError, from_pair)
+
+    def test_tuple_from_pair(self):
+        # Test _PyTuple_FromPair()
+        from_pair = _testinternalcapi.tuple_from_pair
+        self.check_tuple_from_pair(from_pair)
+
+    def test_tuple_from_pair_steal(self):
+        # Test _PyTuple_FromPairSteal()
+        from_pair = _testinternalcapi.tuple_from_pair_steal
+        self.check_tuple_from_pair(from_pair)
+
     def test_tuple_size(self):
         # Test PyTuple_Size()
         size = _testlimitedcapi.tuple_size
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 39be41d9d2a426..0d520684c795d6 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -174,7 +174,7 @@
 @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c 
_xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
-@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c 
_testinternalcapi/test_lock.c _testinternalcapi/pytime.c 
_testinternalcapi/set.c _testinternalcapi/test_critical_sections.c 
_testinternalcapi/complex.c _testinternalcapi/interpreter.c
+@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c 
_testinternalcapi/test_lock.c _testinternalcapi/pytime.c 
_testinternalcapi/set.c _testinternalcapi/test_critical_sections.c 
_testinternalcapi/complex.c _testinternalcapi/interpreter.c 
_testinternalcapi/tuple.c
 @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c 
_testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c 
_testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c 
_testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c 
_testcapi/type.c _testcapi/function.c _testcapi/module.c
 @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c 
_testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c 
_testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c 
_testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c 
_testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c 
_testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c 
_testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c 
_testlimitedcapi/version.c _testlimitedcapi/file.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index b6ed0b8902354e..aa5911ef2fb449 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -2987,6 +2987,9 @@ module_exec(PyObject *module)
     if (_PyTestInternalCapi_Init_CriticalSection(module) < 0) {
         return 1;
     }
+    if (_PyTestInternalCapi_Init_Tuple(module) < 0) {
+        return 1;
+    }
 
     Py_ssize_t sizeof_gc_head = 0;
 #ifndef Py_GIL_DISABLED
diff --git a/Modules/_testinternalcapi/parts.h 
b/Modules/_testinternalcapi/parts.h
index 03557d5bf5957f..81f536c3babb18 100644
--- a/Modules/_testinternalcapi/parts.h
+++ b/Modules/_testinternalcapi/parts.h
@@ -15,5 +15,6 @@ int _PyTestInternalCapi_Init_PyTime(PyObject *module);
 int _PyTestInternalCapi_Init_Set(PyObject *module);
 int _PyTestInternalCapi_Init_Complex(PyObject *module);
 int _PyTestInternalCapi_Init_CriticalSection(PyObject *module);
+int _PyTestInternalCapi_Init_Tuple(PyObject *module);
 
 #endif // Py_TESTINTERNALCAPI_PARTS_H
diff --git a/Modules/_testinternalcapi/tuple.c 
b/Modules/_testinternalcapi/tuple.c
new file mode 100644
index 00000000000000..c12ee32deb9164
--- /dev/null
+++ b/Modules/_testinternalcapi/tuple.c
@@ -0,0 +1,39 @@
+#include "parts.h"
+
+#include "pycore_tuple.h"
+
+
+static PyObject *
+tuple_from_pair(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *first, *second;
+    if (!PyArg_ParseTuple(args, "OO", &first, &second)) {
+        return NULL;
+    }
+
+    return _PyTuple_FromPair(first, second);
+}
+
+static PyObject *
+tuple_from_pair_steal(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *first, *second;
+    if (!PyArg_ParseTuple(args, "OO", &first, &second)) {
+        return NULL;
+    }
+
+    return _PyTuple_FromPairSteal(Py_NewRef(first), Py_NewRef(second));
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"tuple_from_pair", tuple_from_pair, METH_VARARGS},
+    {"tuple_from_pair_steal", tuple_from_pair_steal, METH_VARARGS},
+    {NULL},
+};
+
+int
+_PyTestInternalCapi_Init_Tuple(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index 3c68955495d566..01afa53e15cd5d 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -202,6 +202,35 @@ PyTuple_Pack(Py_ssize_t n, ...)
     return (PyObject *)result;
 }
 
+PyObject *
+_PyTuple_FromPair(PyObject *first, PyObject *second)
+{
+    assert(first != NULL);
+    assert(second != NULL);
+
+    return _PyTuple_FromPairSteal(Py_NewRef(first), Py_NewRef(second));
+}
+
+PyObject *
+_PyTuple_FromPairSteal(PyObject *first, PyObject *second)
+{
+    assert(first != NULL);
+    assert(second != NULL);
+
+    PyTupleObject *op = tuple_alloc(2);
+    if (op == NULL) {
+        Py_DECREF(first);
+        Py_DECREF(second);
+        return NULL;
+    }
+    PyObject **items = op->ob_item;
+    items[0] = first;
+    items[1] = second;
+    if (maybe_tracked(first) || maybe_tracked(second)) {
+        _PyObject_GC_TRACK(op);
+    }
+    return (PyObject *)op;
+}
 
 /* Methods */
 
diff --git a/PCbuild/_testinternalcapi.vcxproj 
b/PCbuild/_testinternalcapi.vcxproj
index 3818e6d3f7bbd2..f3e423fa04668e 100644
--- a/PCbuild/_testinternalcapi.vcxproj
+++ b/PCbuild/_testinternalcapi.vcxproj
@@ -100,6 +100,7 @@
     <ClCompile Include="..\Modules\_testinternalcapi\set.c" />
     <ClCompile Include="..\Modules\_testinternalcapi\complex.c" />
     <ClCompile Include="..\Modules\_testinternalcapi\interpreter.c" />
+    <ClCompile Include="..\Modules\_testinternalcapi\tuple.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testinternalcapi.vcxproj.filters 
b/PCbuild/_testinternalcapi.vcxproj.filters
index 012d709bd1ce5d..7ab242c2c230b6 100644
--- a/PCbuild/_testinternalcapi.vcxproj.filters
+++ b/PCbuild/_testinternalcapi.vcxproj.filters
@@ -27,6 +27,9 @@
     <ClCompile Include="..\Modules\_testinternalcapi\complex.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testinternalcapi\tuple.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc">

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to