Skip to content
Open
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
25 changes: 25 additions & 0 deletions Doc/c-api/dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,31 @@ Dictionary Objects
Return a new empty dictionary, or ``NULL`` on failure.


.. c:function:: PyObject* PyDict_FromKeysAndValues(PyObject *const *keys, PyObject *const *values, Py_ssize_t length)

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

If *length* is ``0``, *keys* and *values* can be NULL.

Return a new dictionary, or ``NULL`` on failure with an exception set.

.. versionadded:: next


.. c:function:: PyObject* PyDict_FromItems(PyObject *const *items, Py_ssize_t length)

Create a dictionary from *items* of *length* pairs (``(key, value)``).

*items* is an array made of keys and values such as:
``key1, value1, key2, value2, ..., keyN, valueN``.

If *length* is ``0``, *items* can be NULL.

Return a new dictionary, or ``NULL`` on failure with an exception set.

.. versionadded:: next


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

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

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

* Add :c:func:`PyDict_FromKeysAndValues` to create a dictionary from an array
of keys and an array of values.
(Contributed by Victor Stinner in :gh:`139772`.)

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

Expand Down
9 changes: 9 additions & 0 deletions Include/cpython/dictobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,12 @@ 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 *items,
Py_ssize_t length);

PyAPI_FUNC(PyObject*) PyDict_FromKeysAndValues(
PyObject *const *keys,
PyObject *const *values,
Py_ssize_t length);
57 changes: 57 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,63 @@ def test_dict_popstring(self):
# CRASHES dict_popstring({}, NULL)
# CRASHES dict_popstring({"a": 1}, NULL)

def test_dict_fromkeysandvalues(self):
# Test PyDict_FromKeysAndValues()
dict_fromkeysandvalues = _testcapi.dict_fromkeysandvalues

d = dict_fromkeysandvalues((), ())
self.assertEqual(d, {})

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

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

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

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

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

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

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

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

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

# test "large" dict (1024 items)
items = []
for key, value in zip(range(1024), map(str, range(1024))):
items.extend((key, value))
d = dict_fromitems(tuple(items))
self.assertEqual(d, {i: str(i) for i in range(1024)})

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

# test invalid arguments
errmsg = "length must be greater than or equal to 0"
with self.assertRaisesRegex(ValueError, errmsg):
dict_fromitems(('x', 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 items.
Patch by Victor Stinner.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyDict_FromKeysAndValues` to create a dictionary from an array
of keys and an array of values. Patch by Victor Stinner.
74 changes: 73 additions & 1 deletion Modules/_testcapi/dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,76 @@ test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored))
}


static PyObject*
dict_fromkeysandvalues(PyObject* self, PyObject *args)
{
PyObject *keys_obj = UNINITIALIZED_PTR, *values_obj = UNINITIALIZED_PTR;
Py_ssize_t length = UNINITIALIZED_SIZE;
if (!PyArg_ParseTuple(args, "|O!O!n",
&PyTuple_Type, &keys_obj,
&PyTuple_Type, &values_obj,
&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 (length == UNINITIALIZED_SIZE) {
if (keys_obj != UNINITIALIZED_PTR) {
length = PyTuple_GET_SIZE(keys_obj);
}
else {
length = 0;
}
}

return PyDict_FromKeysAndValues(keys, values, length);
}


static PyObject*
dict_fromitems(PyObject* self, PyObject *args)
{
PyObject *items_obj = UNINITIALIZED_PTR;
Py_ssize_t length = UNINITIALIZED_SIZE;
if (!PyArg_ParseTuple(args, "|O!n", &PyTuple_Type, &items_obj, &length)) {
return NULL;
}

PyObject **items;
if (items_obj != UNINITIALIZED_PTR) {
items = &PyTuple_GET_ITEM(items_obj, 0);
}
else {
items = NULL;
}

if (length == UNINITIALIZED_SIZE) {
if (items_obj != UNINITIALIZED_PTR) {
length = PyTuple_GET_SIZE(items_obj) / 2;
}
else {
length = 0;
}
}

return PyDict_FromItems(items, length);
}


static PyMethodDef test_methods[] = {
{"dict_containsstring", dict_containsstring, METH_VARARGS},
{"dict_getitemref", dict_getitemref, METH_VARARGS},
Expand All @@ -268,7 +338,9 @@ 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_fromkeysandvalues", dict_fromkeysandvalues, METH_VARARGS},
{"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_offset >= 1);
assert(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 *items, Py_ssize_t length)
{
if (length < 0) {
PyErr_SetString(PyExc_ValueError,
"length must be greater than or equal to 0");
return NULL;
}
return _PyDict_FromItems(items, 2, items + 1, 2, length);
}


PyObject*
PyDict_FromKeysAndValues(PyObject *const *keys, PyObject *const *values,
Py_ssize_t length)
{
if (length < 0) {
PyErr_SetString(PyExc_ValueError,
"length must be greater than or equal to 0");
return NULL;
}
return _PyDict_FromItems(keys, 1, values, 1, 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