Skip to content

Commit

Permalink
pythongh-108314: Add PyDict_ContainsString() function
Browse files Browse the repository at this point in the history
Use PyDict_ContainsString() in pylifecycle.c and pythonrun.c.
  • Loading branch information
vstinner committed Aug 22, 2023
1 parent adfc118 commit 0b41b00
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 18 deletions.
20 changes: 16 additions & 4 deletions Doc/c-api/dict.rst
Expand Up @@ -55,6 +55,15 @@ Dictionary Objects
This is equivalent to the Python expression ``key in p``.
.. c:function:: int PyDict_ContainsString(PyObject *p, const char *key)
This is the same as :c:func:`PyDict_Contains`, but *key* is specified as a
:c:expr:`const char*` UTF-8 encoded bytes string, rather than a
:c:expr:`PyObject*`.
.. versionadded:: 3.13
.. c:function:: PyObject* PyDict_Copy(PyObject *p)
Return a new dictionary that contains the same key-value pairs as *p*.
Expand All @@ -73,7 +82,7 @@ Dictionary Objects
.. index:: single: PyUnicode_FromString()
Insert *val* into the dictionary *p* using *key* as a key. *key* should
be a :c:expr:`const char*`. The key object is created using
be a :c:expr:`const char*` UTF-8 encoded bytes string. The key object is created using
``PyUnicode_FromString(key)``. Return ``0`` on success or ``-1`` on
failure. This function *does not* steal a reference to *val*.
Expand All @@ -88,7 +97,8 @@ Dictionary Objects
.. c:function:: int PyDict_DelItemString(PyObject *p, const char *key)
Remove the entry in dictionary *p* which has a key specified by the string *key*.
Remove the entry in dictionary *p* which has a key specified by the UTF-8
encoded bytes string *key*.
If *key* is not in the dictionary, :exc:`KeyError` is raised.
Return ``0`` on success or ``-1`` on failure.
Expand Down Expand Up @@ -136,7 +146,8 @@ Dictionary Objects
.. c:function:: PyObject* PyDict_GetItemString(PyObject *p, const char *key)
This is the same as :c:func:`PyDict_GetItem`, but *key* is specified as a
:c:expr:`const char*`, rather than a :c:expr:`PyObject*`.
:c:expr:`const char*` UTF-8 encoded bytes string, rather than a
:c:expr:`PyObject*`.
.. note::
Expand All @@ -150,7 +161,8 @@ Dictionary Objects
.. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result)
Similar than :c:func:`PyDict_GetItemRef`, but *key* is specified as a
:c:expr:`const char*`, rather than a :c:expr:`PyObject*`.
:c:expr:`const char*` UTF-8 encoded bytes string, rather than a
:c:expr:`PyObject*`.
.. versionadded:: 3.13
Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Doc/whatsnew/3.13.rst
Expand Up @@ -862,6 +862,11 @@ New Features
not needed.
(Contributed by Victor Stinner in :gh:`106004`.)

* Added :c:func:`PyDict_ContainsString` function: same as
:c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*`
UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`.
(Contributed by Victor Stinner in :gh:`108314`.)

* Add :c:func:`Py_IsFinalizing` function: check if the main Python interpreter is
:term:`shutting down <interpreter shutdown>`.
(Contributed by Victor Stinner in :gh:`108014`.)
Expand Down
1 change: 1 addition & 0 deletions Include/dictobject.h
Expand Up @@ -32,6 +32,7 @@ PyAPI_FUNC(PyObject *) PyDict_Items(PyObject *mp);
PyAPI_FUNC(Py_ssize_t) PyDict_Size(PyObject *mp);
PyAPI_FUNC(PyObject *) PyDict_Copy(PyObject *mp);
PyAPI_FUNC(int) PyDict_Contains(PyObject *mp, PyObject *key);
PyAPI_FUNC(int) PyDict_ContainsString(PyObject *mp, const char *key);

/* PyDict_Update(mp, other) is equivalent to PyDict_Merge(mp, other, 1). */
PyAPI_FUNC(int) PyDict_Update(PyObject *mp, PyObject *other);
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_capi/test_dict.py
Expand Up @@ -213,6 +213,20 @@ def test_dict_contains(self):
# CRASHES contains(42, 'a')
# CRASHES contains(NULL, 'a')

def test_dict_contains_string(self):
contains_string = _testcapi.dict_containsstring
dct = {'a': 1, '\U0001f40d': 2}
self.assertTrue(contains_string(dct, b'a'))
self.assertFalse(contains_string(dct, b'b'))
self.assertTrue(contains_string(dct, '\U0001f40d'.encode()))

dct2 = DictSubclass(dct)
self.assertTrue(contains_string(dct2, b'a'))
self.assertFalse(contains_string(dct2, b'b'))

# CRASHES contains({}, NULL)
# CRASHES contains(NULL, b'a')

def test_dict_setitem(self):
setitem = _testcapi.dict_setitem
dct = {}
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,4 @@
Add :c:func:`PyDict_ContainsString` function: same as
:c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*`
UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`.
Patch by Victor Stinner.
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Expand Up @@ -2450,3 +2450,5 @@
added = '3.13'
[function.PyDict_GetItemStringRef]
added = '3.13'
[function.PyDict_ContainsString]
added = '3.13'
14 changes: 14 additions & 0 deletions Modules/_testcapi/dict.c
Expand Up @@ -74,6 +74,19 @@ dict_contains(PyObject *self, PyObject *args)
RETURN_INT(PyDict_Contains(obj, key));
}

static PyObject *
dict_containsstring(PyObject *self, PyObject *args)
{
PyObject *obj;
const char *key;
Py_ssize_t size;
if (!PyArg_ParseTuple(args, "Oz#", &obj, &key, &size)) {
return NULL;
}
NULLABLE(obj);
RETURN_INT(PyDict_ContainsString(obj, key));
}

static PyObject *
dict_size(PyObject *self, PyObject *obj)
{
Expand Down Expand Up @@ -349,6 +362,7 @@ static PyMethodDef test_methods[] = {
{"dict_getitemref", dict_getitemref, METH_VARARGS},
{"dict_getitemstringref", dict_getitemstringref, METH_VARARGS},
{"dict_contains", dict_contains, METH_VARARGS},
{"dict_containsstring", dict_containsstring, METH_VARARGS},
{"dict_setitem", dict_setitem, METH_VARARGS},
{"dict_setitemstring", dict_setitemstring, METH_VARARGS},
{"dict_delitem", dict_delitem, METH_VARARGS},
Expand Down
12 changes: 12 additions & 0 deletions Objects/dictobject.c
Expand Up @@ -3741,6 +3741,18 @@ PyDict_Contains(PyObject *op, PyObject *key)
return (ix != DKIX_EMPTY && value != NULL);
}

int
PyDict_ContainsString(PyObject *op, const char *key)
{
PyObject *key_obj = PyUnicode_FromString(key);
if (key_obj == NULL) {
return -1;
}
int res = PyDict_Contains(op, key_obj);
Py_DECREF(key_obj);
return res;
}

/* Internal version of PyDict_Contains used when the hash value is already known */
int
_PyDict_Contains_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions Python/pylifecycle.c
Expand Up @@ -2208,10 +2208,11 @@ add_main_module(PyInterpreterState *interp)
}
Py_DECREF(ann_dict);

if (_PyDict_GetItemStringWithError(d, "__builtins__") == NULL) {
if (PyErr_Occurred()) {
return _PyStatus_ERR("Failed to test __main__.__builtins__");
}
int has_builtins = PyDict_ContainsString(d, "__builtins__");
if (has_builtins < 0) {
return _PyStatus_ERR("Failed to test __main__.__builtins__");
}
if (!has_builtins) {
PyObject *bimod = PyImport_ImportModule("builtins");
if (bimod == NULL) {
return _PyStatus_ERR("Failed to retrieve builtins module");
Expand Down
26 changes: 16 additions & 10 deletions Python/pythonrun.c
Expand Up @@ -413,10 +413,11 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
PyObject *dict = PyModule_GetDict(main_module); // borrowed ref

int set_file_name = 0;
if (_PyDict_GetItemStringWithError(dict, "__file__") == NULL) {
if (PyErr_Occurred()) {
goto done;
}
int has_file = PyDict_ContainsString(dict, "__file__");
if (has_file < 0) {
goto done;
}
if (!has_file) {
if (PyDict_SetItemString(dict, "__file__", filename) < 0) {
goto done;
}
Expand Down Expand Up @@ -1713,12 +1714,17 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py
_PyRuntime.signals.unhandled_keyboard_interrupt = 0;

/* Set globals['__builtins__'] if it doesn't exist */
if (globals != NULL && _PyDict_GetItemStringWithError(globals, "__builtins__") == NULL) {
if (PyErr_Occurred() ||
PyDict_SetItemString(globals, "__builtins__",
tstate->interp->builtins) < 0)
{
return NULL;
if (globals != NULL) {
int has_builtins = PyDict_ContainsString(globals, "__builtins__");
if (has_builtins < 0) {
return NULL;
}
if (!has_builtins) {
if (PyDict_SetItemString(globals, "__builtins__",
tstate->interp->builtins) < 0)
{
return NULL;
}
}
}

Expand Down

0 comments on commit 0b41b00

Please sign in to comment.