Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Doc/c-api/dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ Dictionary Objects
Return a new empty dictionary, or ``NULL`` on failure.


.. c:function:: PyObject* PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset, PyObject *const *values, Py_ssize_t values_offset, Py_ssize_t length)

Create a dictionary from *keys* and *values* of *length* items.

*keys_offset* is the offset to access the *keys* array and
*values_offset* is the offset to access the *values* array.
*keys_offset* and *values_offset* must be greater than ``0``.

If *length* is ``0``, *keys*, *keys_offset*, *values* and *values_offset*
arguments are ignored, and an empty dictionary is created.

Return a new dictionary, or ``NULL`` on failure.

.. versionadded:: next


.. c:function:: PyObject* PyDictProxy_New(PyObject *mapping)

Return a :class:`types.MappingProxyType` object for a mapping which
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,10 @@ New features

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

* Add :c:func:`PyDict_FromItems` to create a dictionary from an array of keys
and an array of values.
(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`.)

Expand Down
7 changes: 7 additions & 0 deletions Include/cpython/dictobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,10 @@ PyAPI_FUNC(int) PyDict_ClearWatcher(int watcher_id);
// Mark given dictionary as "watched" (callback will be called if it is modified)
PyAPI_FUNC(int) PyDict_Watch(int watcher_id, PyObject* dict);
PyAPI_FUNC(int) PyDict_Unwatch(int watcher_id, PyObject* dict);

PyAPI_FUNC(PyObject*) PyDict_FromItems(
PyObject *const *keys,
Py_ssize_t keys_offset,
PyObject *const *values,
Py_ssize_t values_offset,
Py_ssize_t length);
45 changes: 45 additions & 0 deletions Lib/test/test_capi/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,51 @@ def test_dict_popstring(self):
# CRASHES dict_popstring({}, NULL)
# CRASHES dict_popstring({"a": 1}, NULL)

def test_dict_fromitems(self):
# Test PyDict_FromItems()
dict_fromitems = _testcapi.dict_fromitems

d = dict_fromitems((), 1, (), 1)
self.assertEqual(d, {})

d = dict_fromitems(tuple(range(1, 4)), 1, tuple('abc'), 1)
self.assertEqual(d, {1: 'a', 2: 'b', 3: 'c'})

# test unicode keys
d = dict_fromitems(tuple('abc'), 1, tuple(range(1, 4)), 1)
self.assertEqual(d, {'a': 1, 'b': 2, 'c': 3})

# test "large" dict (1024 items)
d = dict_fromitems(tuple(range(1024)), 1,
tuple(map(str, range(1024))), 1)
self.assertEqual(d, {i: str(i) for i in range(1024)})

# same array for keys and values with keys_offset=values_offset=2
array = ('a', 1, 'b', 2, 'c', 3)
d = dict_fromitems(array, 2)
self.assertEqual(d, {'a': 1, 'b': 2, 'c': 3})

array = ('a', 1, None, 'b', 2, None, 'c', 3, None)
d = dict_fromitems(array, 3)
self.assertEqual(d, {'a': 1, 'b': 2, 'c': 3})

# Test PyDict_FromItems(NULL, 0, NULL, 0, 0)
d = dict_fromitems()
self.assertEqual(d, {})

# test invalid arguments
errmsg = "keys_offset must be greater than 0"
with self.assertRaisesRegex(ValueError, errmsg):
dict_fromitems(tuple('abc'), 0, tuple(range(1, 4)), 1)

errmsg = "values_offset must be greater than 0"
with self.assertRaisesRegex(ValueError, errmsg):
dict_fromitems(tuple('abc'), 1, tuple(range(1, 4)), 0)

errmsg = "length must be greater than or equal to 0"
with self.assertRaisesRegex(ValueError, errmsg):
dict_fromitems(tuple('abc'), 1, tuple(range(1, 4)), 1, -1)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyDict_FromItems` to create a dictionary from an array of keys and
an array of values. Patch by Victor Stinner.
56 changes: 55 additions & 1 deletion Modules/_testcapi/dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,59 @@ test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored))
}


static PyObject*
dict_fromitems(PyObject* self, PyObject *args)
{
PyObject *keys_obj = UNINITIALIZED_PTR, *values_obj = UNINITIALIZED_PTR;
Py_ssize_t keys_offset = UNINITIALIZED_SIZE, values_offset = UNINITIALIZED_SIZE;
Py_ssize_t length = UNINITIALIZED_SIZE;
if (!PyArg_ParseTuple(args, "|O!nO!nn",
&PyTuple_Type, &keys_obj, &keys_offset,
&PyTuple_Type, &values_obj, &values_offset,
&length)) {
return NULL;
}

PyObject **keys, **values;
if (keys_obj != UNINITIALIZED_PTR) {
keys = &PyTuple_GET_ITEM(keys_obj, 0);
if (values_obj != UNINITIALIZED_PTR) {
values = &PyTuple_GET_ITEM(values_obj, 0);
}
else {
values = keys + 1;
}
}
else {
keys = NULL;
values = NULL;
}

if (keys_offset == UNINITIALIZED_SIZE) {
keys_offset = 0;
}
if (values_offset == UNINITIALIZED_SIZE) {
values_offset = keys_offset;
}

if (length == UNINITIALIZED_SIZE) {
if (keys_obj != UNINITIALIZED_PTR) {
if (keys_offset >= 1) {
length = PyTuple_GET_SIZE(keys_obj) / keys_offset;
}
else {
length = PyTuple_GET_SIZE(keys_obj);
}
}
else {
length = 0;
}
}

return PyDict_FromItems(keys, keys_offset, values, values_offset, length);
}


static PyMethodDef test_methods[] = {
{"dict_containsstring", dict_containsstring, METH_VARARGS},
{"dict_getitemref", dict_getitemref, METH_VARARGS},
Expand All @@ -268,7 +321,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_fromitems", dict_fromitems, METH_VARARGS},
{NULL},
};

Expand Down
30 changes: 30 additions & 0 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2230,6 +2230,10 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
PyObject *const *values, Py_ssize_t values_offset,
Py_ssize_t length)
{
assert(keys == NULL || keys_offset >= 1);
assert(values == NULL || values_offset >= 1);
assert(length >= 0);

bool unicode = true;
PyObject *const *ks = keys;

Expand Down Expand Up @@ -2263,6 +2267,32 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
return dict;
}


PyObject *
PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
PyObject *const *values, Py_ssize_t values_offset,
Py_ssize_t length)
{
if (keys != NULL && keys_offset < 1) {
PyErr_SetString(PyExc_ValueError,
"keys_offset must be greater than 0");
return NULL;
}
if (values != NULL && values_offset < 1) {
PyErr_SetString(PyExc_ValueError,
"values_offset must be greater than 0");
return NULL;
}
if (length < 0) {
PyErr_SetString(PyExc_ValueError,
"length must be greater than or equal to 0");
return NULL;
}

return _PyDict_FromItems(keys, keys_offset, values, values_offset, length);
}


/* Note that, for historical reasons, PyDict_GetItem() suppresses all errors
* that may occur (originally dicts supported only string keys, and exceptions
* weren't possible). So, while the original intent was that a NULL return
Expand Down
Loading