Skip to content

Commit aed66d9

Browse files
committed
Add PyDict_AsFrozenDictAndClear() function
1 parent d3ebabf commit aed66d9

4 files changed

Lines changed: 64 additions & 0 deletions

File tree

‎docs/api.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ Latest version of the header file:
2323
`pythoncapi_compat.h <https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h>`_.
2424

2525

26+
Python 3.16
27+
-----------
28+
29+
.. c:function:: PyObject* PyDict_AsFrozenDictAndClear(PyObject *dict)
30+
31+
See `PyDict_AsFrozenDictAndClear() documentation <https://docs.python.org/dev/c-api/dict.html#c.PyDict_AsFrozenDictAndClear>`__.
32+
33+
2634
Python 3.15
2735
-----------
2836

‎docs/changelog.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Changelog
22
=========
33

4+
* 2026-11-14: Add ``PyDict_AsFrozenDictAndClear()`` function.
45
* 2026-11-04: Add ``Py_SETREF()`` and ``Py_XSETREF()`` to the limited C API
56
older than Python 3.16.
67
* 2026-10-12: Add functions:

‎pythoncapi_compat.h‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3154,6 +3154,42 @@ PyEval_GetFrameBuiltins(void)
31543154
#endif
31553155

31563156

3157+
// Python 3.16 adds PyDict_AsFrozenDictAndClear()
3158+
#if (_Py_PACK_VERSION(3, 15) <= PY_VERSION_HEX \
3159+
&& PY_VERSION_HEX < _Py_PACK_VERSION(3, 16))
3160+
static inline PyObject *
3161+
PyDict_AsFrozenDictAndClear(PyObject *dict)
3162+
{
3163+
if (dict == NULL || !PyDict_Check(dict)) {
3164+
PyErr_BadInternalCall();
3165+
return NULL;
3166+
}
3167+
3168+
#ifndef Py_LIMITED_API
3169+
PyObject *res = PyFrozenDict_New(dict);
3170+
#else
3171+
// PyFrozenDict_New() is not part of the limited C API.
3172+
// Instead, get the built-in "frozendict" type and call frozendict(dict).
3173+
PyObject *builtins = PyEval_GetFrameBuiltins();
3174+
assert(builtins != NULL);
3175+
PyObject *frozendict_type = PyMapping_GetItemString(builtins, "frozendict");
3176+
Py_DECREF(builtins);
3177+
if (frozendict_type == NULL) {
3178+
return NULL;
3179+
}
3180+
3181+
PyObject *res = PyObject_CallOneArg(frozendict_type, dict);
3182+
Py_DECREF(frozendict_type);
3183+
#endif
3184+
if (res == NULL) {
3185+
return NULL;
3186+
}
3187+
PyDict_Clear(dict);
3188+
return res;
3189+
}
3190+
#endif
3191+
3192+
31573193
#ifdef __cplusplus
31583194
}
31593195
#endif

‎tests/test_pythoncapi_compat_cext.c‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,9 @@ test_dict_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
11551155
PyObject *invalid_dict = NULL;
11561156
PyObject *get_value = NULL;
11571157
int res;
1158+
#if _Py_PACK_VERSION(3, 15) <= PY_VERSION_HEX
1159+
PyObject *frozendict = NULL;
1160+
#endif
11581161

11591162
// test PyDict_New()
11601163
dict = PyDict_New();
@@ -1245,6 +1248,22 @@ test_dict_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
12451248
PyErr_Clear();
12461249
assert(get_value == NULL);
12471250

1251+
#if _Py_PACK_VERSION(3, 15) <= PY_VERSION_HEX
1252+
assert(PyDict_Size(dict) == 1);
1253+
frozendict = PyDict_AsFrozenDictAndClear(dict);
1254+
assert(frozendict != NULL);
1255+
#ifndef Py_LIMITED_API
1256+
assert(PyFrozenDict_CheckExact(frozendict));
1257+
#else
1258+
assert(Py_TYPE(frozendict) != &PyDict_Type);
1259+
#endif
1260+
assert(PyDict_GetItemRef(frozendict, key, &get_value) == 1);
1261+
assert(get_value == value);
1262+
Py_DECREF(get_value);
1263+
Py_DECREF(frozendict);
1264+
assert(PyDict_Size(dict) == 0);
1265+
#endif
1266+
12481267
Py_DECREF(dict);
12491268
Py_DECREF(key);
12501269
Py_DECREF(missing_key);

0 commit comments

Comments
 (0)