Skip to content

Commit 32feddb

Browse files
committed
gh-139772: Add PyDict_NewPresized() function
1 parent 6710156 commit 32feddb

File tree

7 files changed

+74
-11
lines changed

7 files changed

+74
-11
lines changed

Doc/c-api/dict.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ Dictionary Objects
3636
Return a new empty dictionary, or ``NULL`` on failure.
3737
3838
39+
.. c:function:: PyObject* PyDict_NewPresized(Py_ssize_t size, int unicode_keys)
40+
41+
Return a new empty dictionary with preallocated items, or ``NULL`` on
42+
failure.
43+
44+
*size* is a hint to preallocate items.
45+
46+
If *unicode_keys* is non-zero, optimize lookup for Unicode keys.
47+
48+
.. versionadded:: next
49+
50+
3951
.. c:function:: PyObject* PyDictProxy_New(PyObject *mapping)
4052
4153
Return a :class:`types.MappingProxyType` object for a mapping which

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,10 @@ New features
860860

861861
(Contributed by Victor Stinner in :gh:`129813`.)
862862

863+
* Add :c:func:`PyDict_NewPresized` function to create a dictionary with
864+
preallocated items.
865+
(Contributed by Victor Stinner in :gh:`139772`.)
866+
863867
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
864868
(Contributed by Victor Stinner in :gh:`111489`.)
865869

Include/cpython/dictobject.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,13 @@ static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) {
6464

6565
PyAPI_FUNC(int) PyDict_ContainsString(PyObject *mp, const char *key);
6666

67-
PyAPI_FUNC(PyObject *) _PyDict_NewPresized(Py_ssize_t minused);
67+
PyAPI_FUNC(PyObject*) PyDict_NewPresized(Py_ssize_t size, int unicode_keys);
68+
69+
Py_DEPRECATED(3.15) static inline PyObject*
70+
_PyDict_NewPresized(Py_ssize_t size)
71+
{
72+
return PyDict_NewPresized(size, 0);
73+
}
6874

6975
PyAPI_FUNC(int) PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result);
7076
PyAPI_FUNC(int) PyDict_PopString(PyObject *dict, const char *key, PyObject **result);

Lib/test/test_capi/test_dict.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,37 @@ def test_dict_popstring(self):
545545
# CRASHES dict_popstring({}, NULL)
546546
# CRASHES dict_popstring({"a": 1}, NULL)
547547

548+
def test_dict_newpresized(self):
549+
# Test PyDict_NewPresized()
550+
dict_newpresized = _testcapi.dict_newpresized
551+
552+
# non-unicode keys
553+
d = dict_newpresized(3, 0)
554+
d[1] = 'a'
555+
d[2] = 'b'
556+
d[3] = 'c'
557+
self.assertEqual(d, {1: 'a', 2: 'b', 3: 'c'})
558+
559+
# unicode keys
560+
d = dict_newpresized(3, 1)
561+
d['a'] = 1
562+
d['b'] = 2
563+
d['c'] = 3
564+
self.assertEqual(d, {'a': 1, 'b': 2, 'c': 3})
565+
566+
# mis-use unicode_keys parameter
567+
d = dict_newpresized(3, 1)
568+
d[1] = 'a'
569+
d[2] = 'b'
570+
d[3] = 'c'
571+
self.assertEqual(d[2], 'b')
572+
573+
# "large" dictionary (1024 items)
574+
d = dict_newpresized(3, 1)
575+
for i in range(1024):
576+
d[str(i)] = i
577+
self.assertEqual(d, {str(i):i for i in range(1024)})
578+
548579

549580
if __name__ == "__main__":
550581
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyDict_NewPresized` function to create a dictionary with
2+
preallocated items. Patch by Victor Stinner.

Modules/_testcapi/dict.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,18 @@ test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored))
258258
}
259259

260260

261+
static PyObject *
262+
dict_newpresized(PyObject *self, PyObject *args)
263+
{
264+
Py_ssize_t size;
265+
int unicode_keys;
266+
if (!PyArg_ParseTuple(args, "ni", &size, &unicode_keys)) {
267+
return NULL;
268+
}
269+
return PyDict_NewPresized(size, unicode_keys);
270+
}
271+
272+
261273
static PyMethodDef test_methods[] = {
262274
{"dict_containsstring", dict_containsstring, METH_VARARGS},
263275
{"dict_getitemref", dict_getitemref, METH_VARARGS},
@@ -268,7 +280,8 @@ static PyMethodDef test_methods[] = {
268280
{"dict_pop_null", dict_pop_null, METH_VARARGS},
269281
{"dict_popstring", dict_popstring, METH_VARARGS},
270282
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
271-
{"test_dict_iteration", test_dict_iteration, METH_NOARGS},
283+
{"test_dict_iteration", test_dict_iteration, METH_NOARGS},
284+
{"dict_newpresized", dict_newpresized, METH_VARARGS},
272285
{NULL},
273286
};
274287

Objects/dictobject.c

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,8 +2191,8 @@ dictresize(PyDictObject *mp,
21912191
return 0;
21922192
}
21932193

2194-
static PyObject *
2195-
dict_new_presized(Py_ssize_t minused, bool unicode)
2194+
PyObject *
2195+
PyDict_NewPresized(Py_ssize_t minused, int unicode_keys)
21962196
{
21972197
const uint8_t log2_max_presize = 17;
21982198
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)
22132213
log2_newsize = estimate_log2_keysize(minused);
22142214
}
22152215

2216-
new_keys = new_keys_object(log2_newsize, unicode);
2216+
new_keys = new_keys_object(log2_newsize, unicode_keys);
22172217
if (new_keys == NULL)
22182218
return NULL;
22192219
return new_dict(new_keys, NULL, 0, 0);
22202220
}
22212221

2222-
PyObject *
2223-
_PyDict_NewPresized(Py_ssize_t minused)
2224-
{
2225-
return dict_new_presized(minused, false);
2226-
}
22272222

22282223
PyObject *
22292224
_PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
@@ -2241,7 +2236,7 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
22412236
ks += keys_offset;
22422237
}
22432238

2244-
PyObject *dict = dict_new_presized(length, unicode);
2239+
PyObject *dict = PyDict_NewPresized(length, unicode);
22452240
if (dict == NULL) {
22462241
return NULL;
22472242
}

0 commit comments

Comments
 (0)