Skip to content

gh-141510: Always return a dict in PyDict_Copy()#145517

Open
vstinner wants to merge 3 commits intopython:mainfrom
vstinner:dict_copy2
Open

gh-141510: Always return a dict in PyDict_Copy()#145517
vstinner wants to merge 3 commits intopython:mainfrom
vstinner:dict_copy2

Conversation

@vstinner
Copy link
Member

@vstinner vstinner commented Mar 4, 2026

  • PyDict_Copy() now also returns a dict if the argument is a frozendict.
  • Remove _PyDict_CopyAsDict() function.
  • Fix frozendict.items() ^ frozendict.items(). Add non-regression test.

📚 Documentation preview 📚: https://cpython-previews--145517.org.readthedocs.build/

* PyDict_Copy() now also returns a dict if the argument is a
  frozendict.
* Remove _PyDict_CopyAsDict() function.
* Fix frozendict.items() ^ frozendict.items(). Add non-regression
  test.
@vstinner
Copy link
Member Author

vstinner commented Mar 4, 2026

I propose changing PyDict_Copy() API to always return a dict. Previously, it returned a frozendict if it was called on a frozendict. IMO it's less surprising and more convenient that PyDict_Copy() always return a copy (as a dict).

Example in typeobject.c:

static PyTypeObject*
type_new_init(type_new_ctx *ctx)
{   
    PyObject *dict = PyDict_Copy(ctx->orig_dict);
    if (dict == NULL) {
        goto error;
    }
    
    ...
    set_tp_dict(type, dict);
    ...
}

The function accepts dict or frozendict but the type dictionary (dict variable) must be a dict.

Example in Modules/_elementtree.c:

static PyObject*
get_attrib_from_keywords(PyObject *kwds)
{
    ...
    if (attrib) {
        ...
        Py_SETREF(attrib, PyDict_Copy(attrib));
    }
    else {
        attrib = PyDict_New();
    }

    if (attrib != NULL && PyDict_Update(attrib, kwds) < 0) {
        Py_DECREF(attrib);
        return NULL;
    }
    return attrib;
}

(With my pending change PR gh-145508), the function accepts dict or frozendict but the result must be a dict (for example, we call PyDict_Update() on it).

cc @corona10 @ZeroIntensity @eendebakpt

@vstinner
Copy link
Member Author

vstinner commented Mar 4, 2026

Currently, PyDict_Copy(frozendict) returns a frozendict copy which is something used internally to modify an immutable frozendict! But I don't think that we should leak such implementation details to the public C API.

Existing C extensions calling PyDict_Copy() expect it to return a dict. If it can return a frozendict on Python 3.15, it can trigger bugs.

Always returning dict avoids these issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant