Search code examples
pythondictionarypython-c-api

How to use PyDict_SetDefault with regard to the refence count of default


I'm wondering about how to use PyDict_SetDefault without creating a reference count catastrophe.

The documentation doesn't say anything about a stolen reference and some testing shows that it doesn't "steal the reference" of default. But it increfs it if and only if the key wasn't present in the dictionary.

That seems really complicated to me because it's just too easy to get wrong. I currently use it like this:

item = PyDict_SetDefault(dictionary, key, defaultvalue);
if (item != defaultvalue) {
    /* key was present, the item is a borrowed reference and default is at refcount 1.*/
    Py_INCREF(item);         /* item at refcount 2 */
    Py_DECREF(defaultvalue); /* default at refcount 0 */
    defaultvalue= NULL;
} else {
    /* key wasn't present, item is default and has refcount 2. */
    defaultvalue = NULL;
}

At the end the dictionary and I own a reference for item and default was cleanly deleted, right?

Is there a better way to deal with the situation without explicitly checking if item == default that I missed? Is it really that complicated or did I miss the obvious and easy way?


Solution

  • item = PyDict_SetDefault(dictionary, key, defaultvalue);
    Py_INCREF(item);
    Py_DECREF(defaultvalue);
    

    It doesn't really matter whether item==defaultvalue - whatever happens you take ownership of item (by increfing it) and release ownership of defaultvalue (by decrefing it, assuming you don't want to use it for anything else).

    if key is present in the dictionary then defaultvalue isn't used, so its refcount stays at 1, and it gets destroyed with decref. item is returned with a refcount of 1 (because it's stored in the dictionary) and we increment it because we're using it too so item now has a refcount of 2.

    If key isn't present then defaultvalue is stored in the dictionary (refcount now 2) used and returned. item and defaultvalue are the same. We increment item (refcount 3) the decref defaultvalue (refcount 2).

    Either way we end in the same place.