GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-141510: Always return a dict in PyDict_Copy() by vstinner · Pull Request #145517 · python/cpython

Conversation

@vstinner

@vstinner vstinner commented Mar 4, 2026 •
edited by github-actions Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member
  • 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.
  • Issue: PEP 814: Add built-in frozendict type #141510

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@vstinner
pythongh-141510: Always return a dict in PyDict_Copy()
0cbef89
* 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
vstinner requested review from ZeroIntensity, markshannon and methane as code owners March 4, 2026 20:12
@vstinner vstinner added the skip news label Mar 4, 2026
@bedevere-app bedevere-app Bot mentioned this pull request Mar 4, 2026
PEP 814: Add built-in frozendict type #141510
Closed
@bedevere-app bedevere-app Bot added the awaiting core review label Mar 4, 2026
@vstinner vstinner mentioned this pull request Mar 4, 2026
gh-141510: Update PyDict_Copy documentation with note on frozendict #145249
Merged
@vstinner

vstinner commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

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

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
Run make clinic
3662ec8
@vstinner vstinner mentioned this pull request Mar 4, 2026
gh-145056: Accept frozendict in xml.etree #145508
Merged
@vstinner
Add more tests
91e9506
@vstinner

vstinner commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner vstinner mentioned this pull request Mar 5, 2026
gh-145119: Allow frozendict to be assigned to instance __dict__ #145123
Closed
@ZeroIntensity

ZeroIntensity commented Mar 5, 2026

Copy link
Copy Markdown
Member

I'm not so sure this is a good idea. If I call something named Copy, I'd expect the result to be the same type as the input. If anything, let's disallow the use of frozendict in PyDict_Copy.

StanFromIreland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

The frozendict C API has only 3 functions: PyFrozenDict_Check(), PyFrozenDict_CheckExact(), PyFrozenDict_New(). For all other methods, it reuses the dict C API (PyDict functions).

PyDict_Copy() is the only function where the return type can depend on the dictionary type (dict or frozendict). IMO it's convenient to also accept frozendict here and convert it to a dict (create a copy). It would be useful in multiple places as I shown in my previous comment.

If anything, let's disallow the use of frozendict in PyDict_Copy.

It would mean that such code (using this PR):

    PyObject *dict;
    dict = PyDict_Copy(orig_dict);
    if (dict == NULL) {
        goto error;
    }

should be replaced with:

    PyObject *dict;
    if (PyFrozenDict_Check(orig_dict)) {
        dict = PyDict_New();
        if (dict == NULL) {
            goto error;
        }
        if (PyDict_Merge(dict, orig_dict, 1) < 0) {
            goto error;
        }
    }
    else {
        dict = PyDict_Copy(orig_dict);
        if (dict == NULL) {
            goto error;
        }
    }

As a temporary solution, I added an internal function _PyDict_CopyAsDict() (in commit 95f56b1).

But I would prefer to have a public C API for such code.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
Complete PyDict_Copy() documentation
de6b0b7
@vstinner

vstinner commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

I completed PyDict_Copy() documentation.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

Another example in PR gh-145123:

    else if (PyDict_Check(dict)) {
        /* Copy __dict__ to avoid mutating it. */
        PyObject *temp = PyDict_Copy(dict);
        Py_SETREF(dict, temp);
    }
    else if (PyFrozenDict_Check(dict)) {
        /* Convert frozendict to a mutable dict for merging. */
        PyObject *temp = PyDict_New();
        if (temp != NULL && PyDict_Update(temp, dict) < 0) {
            Py_DECREF(temp);
            temp = NULL;
        }
        Py_SETREF(dict, temp);
    }
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

If anything, let's disallow the use of frozendict in PyDict_Copy.

And here is something completely different! I wrote #145531 to add PyFrozenDict_AsDict() function and modify PyDict_Copy() to only accept dict.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@corona10

corona10 commented Mar 5, 2026

Copy link
Copy Markdown
Member

Why is frozen dict needed to be copied? It's immutable.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

corona10
corona10 reviewed Mar 5, 2026
View reviewed changes
Comment thread Objects/dictobject.c

return _PyDict_Copy(o);
PyObject *res;
Py_BEGIN_CRITICAL_SECTION(o);

@corona10 corona10 Mar 5, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even we want to copy as dict from frozendict, critical section is actually needed for frozendict?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@vstinner vstinner Mar 5, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to skip the critical section for frozendict, at least for the PyFrozenDict_CheckExact() case. But I would prefer to work on such optimization in a separated PR since this PR (and PR gh-145531) is already quite complex.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@vstinner

vstinner commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

Why is frozen dict needed to be copied? It's immutable.

Multiple functions accept dict and frozendict (see previous comments), but then only want to work on a dict copy and so use PyDict_Copy(). The question is which API do we want to use to convert a frozendict to a dict. Here I propose to accept frozendict in PyDict_Copy(), but convert it to dict.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ZeroIntensity

ZeroIntensity commented Mar 5, 2026

Copy link
Copy Markdown
Member

PyDict_Copy() is the only function where the return type can depend on the dictionary type (dict or frozendict). IMO it's convenient to also accept frozendict here and convert it to a dict (create a copy). It would be useful in multiple places as I shown in my previous comment.

Not disagreeing that it's useful, but it's also a huge footgun. I think it's really counterintuitive for a copy to have a different type than the input. That, and how are users supposed to actually create a copy of frozendict?

I'd be okay with it if we did the following:

  1. Implement this PR, where passing a frozendict to PyDict_Copy always returns a dict.
  2. Add a PyFrozenDict_Copy function, where passing a frozendict or a dict always returns a frozendict.
  3. Add a PyAnyDict_Copy function, which depends on the type of the input (dict -> dict, frozendict -> frozendict).

With the existence of those two, the expected behavior is much more intuitive.

eendebakpt reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

@ZeroIntensity and @StanFromIreland would prefer to not accept frozendict in PyDict_Copy(). I convinced myself that's the right way. For example, copy.copy() returns the same as the input. So I close this PR. Instead, I created PR gh-145542 to no longer accept frozendict in PyDict_Copy().

I suggest to continue the discussion in PR gh-145531 about adding a new function to convert a frozendict to a dict.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner vstinner closed this Mar 5, 2026
@vstinner
vstinner deleted the dict_copy2 branch March 5, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@corona10 corona10 corona10 left review comments
@markshannon markshannon Awaiting requested review from markshannon markshannon is a code owner
@methane methane Awaiting requested review from methane methane is a code owner
@ZeroIntensity ZeroIntensity Awaiting requested review from ZeroIntensity ZeroIntensity is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting core review skip news

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@vstinner @ZeroIntensity @corona10
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.