Skip to content

Commit 4615e77

Browse files
committed
gh-139772: Add PyDict_FromItems() function
1 parent 5776d0d commit 4615e77

File tree

7 files changed

+72
-1
lines changed

7 files changed

+72
-1
lines changed

Doc/c-api/dict.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ Dictionary Objects
3636
Return a new empty dictionary, or ``NULL`` on failure.
3737
3838
39+
.. c:function:: PyObject* PyDict_FromItems(PyObject *const *keys, PyObject *const *values, Py_ssize_t length)
40+
41+
Create a dictionary from *keys* and *values* of *length* items.
42+
43+
Return a new empty dictionary, or ``NULL`` on failure.
44+
45+
.. versionadded:: next
46+
47+
3948
.. c:function:: PyObject* PyDictProxy_New(PyObject *mapping)
4049
4150
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
@@ -852,6 +852,10 @@ New features
852852

853853
(Contributed by Victor Stinner in :gh:`129813`.)
854854

855+
* Add :c:func:`PyDict_FromItems(keys, values, length) <PyDict_FromItems>`
856+
to create a dictionary from *keys* and *values* of *length* items.
857+
(Contributed by Victor Stinner in :gh:`139772`.)
858+
855859
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
856860
(Contributed by Victor Stinner in :gh:`111489`.)
857861

Include/cpython/dictobject.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,8 @@ PyAPI_FUNC(int) PyDict_ClearWatcher(int watcher_id);
103103
// Mark given dictionary as "watched" (callback will be called if it is modified)
104104
PyAPI_FUNC(int) PyDict_Watch(int watcher_id, PyObject* dict);
105105
PyAPI_FUNC(int) PyDict_Unwatch(int watcher_id, PyObject* dict);
106+
107+
PyAPI_FUNC(PyObject *) PyDict_FromItems(
108+
PyObject *const *keys,
109+
PyObject *const *values,
110+
Py_ssize_t length);

Lib/test/test_capi/test_dict.py

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

548+
def test_dict_fromitems(self):
549+
dict_fromitems = _testcapi.dict_fromitems
550+
551+
d = dict_fromitems((), ())
552+
self.assertEqual(d, {})
553+
554+
d = dict_fromitems(tuple(range(1, 4)), tuple('abc'))
555+
self.assertEqual(d, {1: 'a', 2: 'b', 3: 'c'})
556+
557+
# test unicode keys
558+
d = dict_fromitems(tuple('abc'), tuple(range(1, 4)))
559+
self.assertEqual(d, {'a': 1, 'b': 2, 'c': 3})
560+
561+
# test "large" dict (1024 items)
562+
d = dict_fromitems(tuple(range(1024)), tuple(map(str, range(1024))))
563+
self.assertEqual(d, {i: str(i) for i in range(1024)})
564+
548565

549566
if __name__ == "__main__":
550567
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PyDict_FromItems(keys, values, length) <PyDict_FromItems>` to
2+
create a dictionary from *keys* and *values* of *length* items. Patch by
3+
Victor Stinner.

Modules/_testcapi/dict.c

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

260260

261+
static PyObject*
262+
dict_fromitems(PyObject* self, PyObject *args)
263+
{
264+
PyObject *keys_obj, *values_obj;
265+
if (!PyArg_ParseTuple(args, "O!O!",
266+
&PyTuple_Type, &keys_obj,
267+
&PyTuple_Type, &values_obj)) {
268+
return NULL;
269+
}
270+
271+
Py_ssize_t length = PyTuple_GET_SIZE(keys_obj);
272+
if (PyTuple_GET_SIZE(values_obj) != length) {
273+
PyErr_SetString(PyExc_ValueError,
274+
"keys and values must have the same length");
275+
return NULL;
276+
}
277+
278+
PyObject **keys = &PyTuple_GET_ITEM(keys_obj, 0);
279+
PyObject **values = &PyTuple_GET_ITEM(values_obj, 0);
280+
return PyDict_FromItems(keys, values, length);
281+
}
282+
283+
261284
static PyMethodDef test_methods[] = {
262285
{"dict_containsstring", dict_containsstring, METH_VARARGS},
263286
{"dict_getitemref", dict_getitemref, METH_VARARGS},
@@ -268,7 +291,8 @@ static PyMethodDef test_methods[] = {
268291
{"dict_pop_null", dict_pop_null, METH_VARARGS},
269292
{"dict_popstring", dict_popstring, METH_VARARGS},
270293
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
271-
{"test_dict_iteration", test_dict_iteration, METH_NOARGS},
294+
{"test_dict_iteration", test_dict_iteration, METH_NOARGS},
295+
{"dict_fromitems", dict_fromitems, METH_VARARGS},
272296
{NULL},
273297
};
274298

Objects/dictobject.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2263,6 +2263,15 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
22632263
return dict;
22642264
}
22652265

2266+
2267+
PyObject *
2268+
PyDict_FromItems(PyObject *const *keys, PyObject *const *values,
2269+
Py_ssize_t length)
2270+
{
2271+
return _PyDict_FromItems(keys, 1, values, 1, length);
2272+
}
2273+
2274+
22662275
/* Note that, for historical reasons, PyDict_GetItem() suppresses all errors
22672276
* that may occur (originally dicts supported only string keys, and exceptions
22682277
* weren't possible). So, while the original intent was that a NULL return

0 commit comments

Comments
 (0)