Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit de61d4b

Browse files
colesburyencukou
andauthored
gh-112066: Add PyDict_SetDefaultRef function. (#112123)
The `PyDict_SetDefaultRef` function is similar to `PyDict_SetDefault`, but returns a strong reference through the optional `**result` pointer instead of a borrowed reference. Co-authored-by: Petr Viktorin <[email protected]>
1 parent 0e2ab73 commit de61d4b

File tree

7 files changed

+160
-20
lines changed

7 files changed

+160
-20
lines changed

Doc/c-api/dict.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,26 @@ Dictionary Objects
174174
.. versionadded:: 3.4
175175
176176
177+
.. c:function:: int PyDict_SetDefaultRef(PyObject *p, PyObject *key, PyObject *default_value, PyObject **result)
178+
179+
Inserts *default_value* into the dictionary *p* with a key of *key* if the
180+
key is not already present in the dictionary. If *result* is not ``NULL``,
181+
then *\*result* is set to a :term:`strong reference` to either
182+
*default_value*, if the key was not present, or the existing value, if *key*
183+
was already present in the dictionary.
184+
Returns ``1`` if the key was present and *default_value* was not inserted,
185+
or ``0`` if the key was not present and *default_value* was inserted.
186+
On failure, returns ``-1``, sets an exception, and sets ``*result``
187+
to ``NULL``.
188+
189+
For clarity: if you have a strong reference to *default_value* before
190+
calling this function, then after it returns, you hold a strong reference
191+
to both *default_value* and *\*result* (if it's not ``NULL``).
192+
These may refer to the same object: in that case you hold two separate
193+
references to it.
194+
.. versionadded:: 3.13
195+
196+
177197
.. c:function:: int PyDict_Pop(PyObject *p, PyObject *key, PyObject **result)
178198
179199
Remove *key* from dictionary *p* and optionally return the removed value.

Doc/whatsnew/3.13.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,12 @@ New Features
14401440
not needed.
14411441
(Contributed by Victor Stinner in :gh:`106004`.)
14421442

1443+
* Added :c:func:`PyDict_SetDefaultRef`, which is similar to
1444+
:c:func:`PyDict_SetDefault` but returns a :term:`strong reference` instead of
1445+
a :term:`borrowed reference`. This function returns ``-1`` on error, ``0`` on
1446+
insertion, and ``1`` if the key was already present in the dictionary.
1447+
(Contributed by Sam Gross in :gh:`112066`.)
1448+
14431449
* Add :c:func:`PyDict_ContainsString` function: same as
14441450
:c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*`
14451451
UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`.

Include/cpython/dictobject.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ PyAPI_FUNC(PyObject *) _PyDict_GetItemStringWithError(PyObject *, const char *);
4141
PyAPI_FUNC(PyObject *) PyDict_SetDefault(
4242
PyObject *mp, PyObject *key, PyObject *defaultobj);
4343

44+
// Inserts `key` with a value `default_value`, if `key` is not already present
45+
// in the dictionary. If `result` is not NULL, then the value associated
46+
// with `key` is returned in `*result` (either the existing value, or the now
47+
// inserted `default_value`).
48+
// Returns:
49+
// -1 on error
50+
// 0 if `key` was not present and `default_value` was inserted
51+
// 1 if `key` was present and `default_value` was not inserted
52+
PyAPI_FUNC(int) PyDict_SetDefaultRef(PyObject *mp, PyObject *key, PyObject *default_value, PyObject **result);
53+
4454
/* Get the number of items of a dictionary. */
4555
static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) {
4656
PyDictObject *mp;

Lib/test/test_capi/test_dict.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,28 @@ def test_dict_setdefault(self):
339339
# CRASHES setdefault({}, 'a', NULL)
340340
# CRASHES setdefault(NULL, 'a', 5)
341341

342+
def test_dict_setdefaultref(self):
343+
setdefault = _testcapi.dict_setdefaultref
344+
dct = {}
345+
self.assertEqual(setdefault(dct, 'a', 5), 5)
346+
self.assertEqual(dct, {'a': 5})
347+
self.assertEqual(setdefault(dct, 'a', 8), 5)
348+
self.assertEqual(dct, {'a': 5})
349+
350+
dct2 = DictSubclass()
351+
self.assertEqual(setdefault(dct2, 'a', 5), 5)
352+
self.assertEqual(dct2, {'a': 5})
353+
self.assertEqual(setdefault(dct2, 'a', 8), 5)
354+
self.assertEqual(dct2, {'a': 5})
355+
356+
self.assertRaises(TypeError, setdefault, {}, [], 5) # unhashable
357+
self.assertRaises(SystemError, setdefault, UserDict(), 'a', 5)
358+
self.assertRaises(SystemError, setdefault, [1], 0, 5)
359+
self.assertRaises(SystemError, setdefault, 42, 'a', 5)
360+
# CRASHES setdefault({}, NULL, 5)
361+
# CRASHES setdefault({}, 'a', NULL)
362+
# CRASHES setdefault(NULL, 'a', 5)
363+
342364
def test_mapping_keys_valuesitems(self):
343365
class BadMapping(dict):
344366
def keys(self):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Add :c:func:`PyDict_SetDefaultRef`: insert a key and value into a dictionary
2+
if the key is not already present. This is similar to
3+
:meth:`dict.setdefault`, but returns an integer value indicating if the key
4+
was already present. It is also similar to :c:func:`PyDict_SetDefault`, but
5+
returns a strong reference instead of a borrowed reference.

Modules/_testcapi/dict.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,31 @@ dict_setdefault(PyObject *self, PyObject *args)
225225
return PyDict_SetDefault(mapping, key, defaultobj);
226226
}
227227

228+
static PyObject *
229+
dict_setdefaultref(PyObject *self, PyObject *args)
230+
{
231+
PyObject *obj, *key, *default_value, *result = UNINITIALIZED_PTR;
232+
if (!PyArg_ParseTuple(args, "OOO", &obj, &key, &default_value)) {
233+
return NULL;
234+
}
235+
NULLABLE(obj);
236+
NULLABLE(key);
237+
NULLABLE(default_value);
238+
switch (PyDict_SetDefaultRef(obj, key, default_value, &result)) {
239+
case -1:
240+
assert(result == NULL);
241+
return NULL;
242+
case 0:
243+
assert(result == default_value);
244+
return result;
245+
case 1:
246+
return result;
247+
default:
248+
Py_FatalError("PyDict_SetDefaultRef() returned invalid code");
249+
Py_UNREACHABLE();
250+
}
251+
}
252+
228253
static PyObject *
229254
dict_delitem(PyObject *self, PyObject *args)
230255
{
@@ -433,6 +458,7 @@ static PyMethodDef test_methods[] = {
433458
{"dict_delitem", dict_delitem, METH_VARARGS},
434459
{"dict_delitemstring", dict_delitemstring, METH_VARARGS},
435460
{"dict_setdefault", dict_setdefault, METH_VARARGS},
461+
{"dict_setdefaultref", dict_setdefaultref, METH_VARARGS},
436462
{"dict_keys", dict_keys, METH_O},
437463
{"dict_values", dict_values, METH_O},
438464
{"dict_items", dict_items, METH_O},

Objects/dictobject.c

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3355,8 +3355,9 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
33553355
return Py_NewRef(val);
33563356
}
33573357

3358-
PyObject *
3359-
PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
3358+
static int
3359+
dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value,
3360+
PyObject **result, int incref_result)
33603361
{
33613362
PyDictObject *mp = (PyDictObject *)d;
33623363
PyObject *value;
@@ -3365,41 +3366,64 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
33653366

33663367
if (!PyDict_Check(d)) {
33673368
PyErr_BadInternalCall();
3368-
return NULL;
3369+
if (result) {
3370+
*result = NULL;
3371+
}
3372+
return -1;
33693373
}
33703374

33713375
if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) {
33723376
hash = PyObject_Hash(key);
3373-
if (hash == -1)
3374-
return NULL;
3377+
if (hash == -1) {
3378+
if (result) {
3379+
*result = NULL;
3380+
}
3381+
return -1;
3382+
}
33753383
}
33763384

33773385
if (mp->ma_keys == Py_EMPTY_KEYS) {
33783386
if (insert_to_emptydict(interp, mp, Py_NewRef(key), hash,
3379-
Py_NewRef(defaultobj)) < 0) {
3380-
return NULL;
3387+
Py_NewRef(default_value)) < 0) {
3388+
if (result) {
3389+
*result = NULL;
3390+
}
3391+
return -1;
3392+
}
3393+
if (result) {
3394+
*result = incref_result ? Py_NewRef(default_value) : default_value;
33813395
}
3382-
return defaultobj;
3396+
return 0;
33833397
}
33843398

33853399
if (!PyUnicode_CheckExact(key) && DK_IS_UNICODE(mp->ma_keys)) {
33863400
if (insertion_resize(interp, mp, 0) < 0) {
3387-
return NULL;
3401+
if (result) {
3402+
*result = NULL;
3403+
}
3404+
return -1;
33883405
}
33893406
}
33903407

33913408
Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value);
3392-
if (ix == DKIX_ERROR)
3393-
return NULL;
3409+
if (ix == DKIX_ERROR) {
3410+
if (result) {
3411+
*result = NULL;
3412+
}
3413+
return -1;
3414+
}
33943415

33953416
if (ix == DKIX_EMPTY) {
33963417
uint64_t new_version = _PyDict_NotifyEvent(
3397-
interp, PyDict_EVENT_ADDED, mp, key, defaultobj);
3418+
interp, PyDict_EVENT_ADDED, mp, key, default_value);
33983419
mp->ma_keys->dk_version = 0;
3399-
value = defaultobj;
3420+
value = default_value;
34003421
if (mp->ma_keys->dk_usable <= 0) {
34013422
if (insertion_resize(interp, mp, 1) < 0) {
3402-
return NULL;
3423+
if (result) {
3424+
*result = NULL;
3425+
}
3426+
return -1;
34033427
}
34043428
}
34053429
Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
@@ -3431,22 +3455,50 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
34313455
mp->ma_keys->dk_usable--;
34323456
mp->ma_keys->dk_nentries++;
34333457
assert(mp->ma_keys->dk_usable >= 0);
3458+
ASSERT_CONSISTENT(mp);
3459+
if (result) {
3460+
*result = incref_result ? Py_NewRef(value) : value;
3461+
}
3462+
return 0;
34343463
}
34353464
else if (value == NULL) {
34363465
uint64_t new_version = _PyDict_NotifyEvent(
3437-
interp, PyDict_EVENT_ADDED, mp, key, defaultobj);
3438-
value = defaultobj;
3466+
interp, PyDict_EVENT_ADDED, mp, key, default_value);
3467+
value = default_value;
34393468
assert(_PyDict_HasSplitTable(mp));
34403469
assert(mp->ma_values->values[ix] == NULL);
34413470
MAINTAIN_TRACKING(mp, key, value);
34423471
mp->ma_values->values[ix] = Py_NewRef(value);
34433472
_PyDictValues_AddToInsertionOrder(mp->ma_values, ix);
34443473
mp->ma_used++;
34453474
mp->ma_version_tag = new_version;
3475+
ASSERT_CONSISTENT(mp);
3476+
if (result) {
3477+
*result = incref_result ? Py_NewRef(value) : value;
3478+
}
3479+
return 0;
34463480
}
34473481

34483482
ASSERT_CONSISTENT(mp);
3449-
return value;
3483+
if (result) {
3484+
*result = incref_result ? Py_NewRef(value) : value;
3485+
}
3486+
return 1;
3487+
}
3488+
3489+
int
3490+
PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value,
3491+
PyObject **result)
3492+
{
3493+
return dict_setdefault_ref(d, key, default_value, result, 1);
3494+
}
3495+
3496+
PyObject *
3497+
PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
3498+
{
3499+
PyObject *result;
3500+
dict_setdefault_ref(d, key, defaultobj, &result, 0);
3501+
return result;
34503502
}
34513503

34523504
/*[clinic input]
@@ -3467,9 +3519,8 @@ dict_setdefault_impl(PyDictObject *self, PyObject *key,
34673519
/*[clinic end generated code: output=f8c1101ebf69e220 input=0f063756e815fd9d]*/
34683520
{
34693521
PyObject *val;
3470-
3471-
val = PyDict_SetDefault((PyObject *)self, key, default_value);
3472-
return Py_XNewRef(val);
3522+
PyDict_SetDefaultRef((PyObject *)self, key, default_value, &val);
3523+
return val;
34733524
}
34743525

34753526

0 commit comments

Comments
 (0)