From 8a61f5aa3cc81081185a694f0be9a0c9e390b515 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 8 Oct 2025 15:25:26 +0200 Subject: [PATCH 1/2] gh-139772: Add PyDict_NewPresized() function --- Doc/c-api/dict.rst | 12 +++++++ Doc/whatsnew/3.15.rst | 6 ++++ Include/cpython/dictobject.h | 8 ++++- Lib/test/test_capi/test_dict.py | 31 +++++++++++++++++++ ...-10-08-15-26-21.gh-issue-139772.HBZeQA.rst | 2 ++ ...-10-12-14-38-56.gh-issue-139772.rIdTy5.rst | 2 ++ Modules/_testcapi/dict.c | 15 ++++++++- Objects/dictobject.c | 13 +++----- 8 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-10-08-15-26-21.gh-issue-139772.HBZeQA.rst create mode 100644 Misc/NEWS.d/next/C_API/2025-10-12-14-38-56.gh-issue-139772.rIdTy5.rst diff --git a/Doc/c-api/dict.rst b/Doc/c-api/dict.rst index 0fbe26b56c0a7c..5bbfc9bdd2f7c9 100644 --- a/Doc/c-api/dict.rst +++ b/Doc/c-api/dict.rst @@ -36,6 +36,18 @@ Dictionary Objects Return a new empty dictionary, or ``NULL`` on failure. +.. c:function:: PyObject* PyDict_NewPresized(Py_ssize_t size, int unicode_keys) + + Return a new empty dictionary with preallocated items, or ``NULL`` on + failure. + + *size* is a hint to preallocate items. + + If *unicode_keys* is non-zero, optimize lookup for Unicode keys. + + .. versionadded:: next + + .. c:function:: PyObject* PyDictProxy_New(PyObject *mapping) Return a :class:`types.MappingProxyType` object for a mapping which diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index a6be27162965ea..8c88c334fa0180 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -860,6 +860,10 @@ New features (Contributed by Victor Stinner in :gh:`129813`.) +* Add :c:func:`PyDict_NewPresized` function to create a dictionary with + preallocated items. + (Contributed by Victor Stinner in :gh:`139772`.) + * Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array. (Contributed by Victor Stinner in :gh:`111489`.) @@ -880,6 +884,8 @@ Porting to Python 3.15 * Private functions promoted to public C APIs: + * :c:func:`_PyDict_NewPresized`: use :c:func:`PyDict_NewPresized`. + The |pythoncapi_compat_project| can be used to get most of these new functions on Python 3.14 and older. diff --git a/Include/cpython/dictobject.h b/Include/cpython/dictobject.h index df9ec7050fca1a..5d9b7dccb0fa9a 100644 --- a/Include/cpython/dictobject.h +++ b/Include/cpython/dictobject.h @@ -64,7 +64,13 @@ static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) { PyAPI_FUNC(int) PyDict_ContainsString(PyObject *mp, const char *key); -PyAPI_FUNC(PyObject *) _PyDict_NewPresized(Py_ssize_t minused); +PyAPI_FUNC(PyObject*) PyDict_NewPresized(Py_ssize_t size, int unicode_keys); + +Py_DEPRECATED(3.15) static inline PyObject* +_PyDict_NewPresized(Py_ssize_t size) +{ + return PyDict_NewPresized(size, 0); +} PyAPI_FUNC(int) PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result); PyAPI_FUNC(int) PyDict_PopString(PyObject *dict, const char *key, PyObject **result); diff --git a/Lib/test/test_capi/test_dict.py b/Lib/test/test_capi/test_dict.py index e726e3d813d888..887108a623760b 100644 --- a/Lib/test/test_capi/test_dict.py +++ b/Lib/test/test_capi/test_dict.py @@ -545,6 +545,37 @@ def test_dict_popstring(self): # CRASHES dict_popstring({}, NULL) # CRASHES dict_popstring({"a": 1}, NULL) + def test_dict_newpresized(self): + # Test PyDict_NewPresized() + dict_newpresized = _testcapi.dict_newpresized + + # non-unicode keys + d = dict_newpresized(3, 0) + d[1] = 'a' + d[2] = 'b' + d[3] = 'c' + self.assertEqual(d, {1: 'a', 2: 'b', 3: 'c'}) + + # unicode keys + d = dict_newpresized(3, 1) + d['a'] = 1 + d['b'] = 2 + d['c'] = 3 + self.assertEqual(d, {'a': 1, 'b': 2, 'c': 3}) + + # mis-use unicode_keys parameter + d = dict_newpresized(3, 1) + d[1] = 'a' + d[2] = 'b' + d[3] = 'c' + self.assertEqual(d[2], 'b') + + # "large" dictionary (1024 items) + d = dict_newpresized(3, 1) + for i in range(1024): + d[str(i)] = i + self.assertEqual(d, {str(i):i for i in range(1024)}) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C_API/2025-10-08-15-26-21.gh-issue-139772.HBZeQA.rst b/Misc/NEWS.d/next/C_API/2025-10-08-15-26-21.gh-issue-139772.HBZeQA.rst new file mode 100644 index 00000000000000..2f0ed3a21d75da --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-10-08-15-26-21.gh-issue-139772.HBZeQA.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyDict_NewPresized` function to create a dictionary with +preallocated items. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C_API/2025-10-12-14-38-56.gh-issue-139772.rIdTy5.rst b/Misc/NEWS.d/next/C_API/2025-10-12-14-38-56.gh-issue-139772.rIdTy5.rst new file mode 100644 index 00000000000000..e73d6ffeff5c15 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-10-12-14-38-56.gh-issue-139772.rIdTy5.rst @@ -0,0 +1,2 @@ +Deprecate the private :c:func:`!_PyDict_NewPresized` function: use the new +public :c:func:`PyDict_NewPresized` instead. Patch by Victor Stinner. diff --git a/Modules/_testcapi/dict.c b/Modules/_testcapi/dict.c index b7c73d7332bd4e..795b2e67b67a99 100644 --- a/Modules/_testcapi/dict.c +++ b/Modules/_testcapi/dict.c @@ -258,6 +258,18 @@ test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored)) } +static PyObject * +dict_newpresized(PyObject *self, PyObject *args) +{ + Py_ssize_t size; + int unicode_keys; + if (!PyArg_ParseTuple(args, "ni", &size, &unicode_keys)) { + return NULL; + } + return PyDict_NewPresized(size, unicode_keys); +} + + static PyMethodDef test_methods[] = { {"dict_containsstring", dict_containsstring, METH_VARARGS}, {"dict_getitemref", dict_getitemref, METH_VARARGS}, @@ -268,7 +280,8 @@ static PyMethodDef test_methods[] = { {"dict_pop_null", dict_pop_null, METH_VARARGS}, {"dict_popstring", dict_popstring, METH_VARARGS}, {"dict_popstring_null", dict_popstring_null, METH_VARARGS}, - {"test_dict_iteration", test_dict_iteration, METH_NOARGS}, + {"test_dict_iteration", test_dict_iteration, METH_NOARGS}, + {"dict_newpresized", dict_newpresized, METH_VARARGS}, {NULL}, }; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 24188ffe7132d5..fd2477c5dbe8a2 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -2191,8 +2191,8 @@ dictresize(PyDictObject *mp, return 0; } -static PyObject * -dict_new_presized(Py_ssize_t minused, bool unicode) +PyObject * +PyDict_NewPresized(Py_ssize_t minused, int unicode_keys) { const uint8_t log2_max_presize = 17; const Py_ssize_t max_presize = ((Py_ssize_t)1) << log2_max_presize; @@ -2213,17 +2213,12 @@ dict_new_presized(Py_ssize_t minused, bool unicode) log2_newsize = estimate_log2_keysize(minused); } - new_keys = new_keys_object(log2_newsize, unicode); + new_keys = new_keys_object(log2_newsize, unicode_keys); if (new_keys == NULL) return NULL; return new_dict(new_keys, NULL, 0, 0); } -PyObject * -_PyDict_NewPresized(Py_ssize_t minused) -{ - return dict_new_presized(minused, false); -} PyObject * _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset, @@ -2241,7 +2236,7 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset, ks += keys_offset; } - PyObject *dict = dict_new_presized(length, unicode); + PyObject *dict = PyDict_NewPresized(length, unicode); if (dict == NULL) { return NULL; } From 4b34da958af12e13942c6ebccd6a09d229e2fd84 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 12 Oct 2025 19:40:02 +0200 Subject: [PATCH 2/2] Fix the doc --- Doc/whatsnew/3.15.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 8c88c334fa0180..487179c004eaaf 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -884,7 +884,7 @@ Porting to Python 3.15 * Private functions promoted to public C APIs: - * :c:func:`_PyDict_NewPresized`: use :c:func:`PyDict_NewPresized`. + * :c:func:`!_PyDict_NewPresized`: use :c:func:`PyDict_NewPresized`. The |pythoncapi_compat_project| can be used to get most of these new functions on Python 3.14 and older.