Skip to content

fix: use original dict #5658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 13, 2025
Merged

Conversation

henryiii
Copy link
Collaborator

Description

Pulled from #5646, I think this might make sense in general. By replacing __dict__, we throw away the original specialized dict that can do key sharing. We do need to update instead, but I think this makes sense. ChatGPT also liked it. https://chatgpt.com/share/6822d35a-c8b4-8012-8b97-6b387013ea09

Suggested changelog entry:

* Update the dict when restoring pickles, instead of assigning a replacement dict.

@@ -451,7 +451,10 @@ void setstate(value_and_holder &v_h, std::pair<T, O> &&result, bool need_alias)
// See PR #2972 for details.
return;
}
setattr((PyObject *) v_h.inst, "__dict__", d);
auto dict = getattr((PyObject *) v_h.inst, "__dict__");
Copy link
Collaborator

Choose a reason for hiding this comment

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

For general background (I looked it up because I was curious), this code goes back to #2972, which in turn goes all the way back to commit c10ac6c.

I'm wondering: Could it be that v_h.inst does not already have a __dict__? — I don't know, TBH.

If it does not, the AttributeError will not be very helpful to most users.

What do you think about a more conservative change?

For example (untested):

    auto dict = getattr((PyObject *) v_h.inst, "__dict__", py::none());
    if (dict.is_none()) {
        setattr((PyObject *) v_h.inst, "__dict__", d);
    } else {
        if (PyDict_Update(dict.ptr(), d.ptr()) < 0) {
            throw error_already_set();
        }
    }

(I think so close to a major release, it's best to err on the conservative side.)


After writing the above, I ran it by ChatGPT, this came back:


Technical Insight: Is __dict__ guaranteed?

In general, no, an instance's __dict__ is not guaranteed to exist until it has been accessed or an attribute has been set on the instance. Specifically:

  • Many built-in types and C extension types do not have __dict__ unless they were defined with tp_dictoffset, and even then it may be nullptr until first use.

  • Even for py::class_-wrapped user-defined classes, the backing C++ instance might not have initialized its __dict__ until Python accesses it.

  • getattr(obj, "__dict__") raises AttributeError if no __dict__ exists or is inaccessible.


I'm not sure if all of this is actually true, for Python 3.8+, but it makes me feel even stronger that the conservative approach is much less risky.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it is, at least I know it is if py::dynamic_attr is set. If it's not set, I don't think you can run this directly anyway, but I'll go the conservative route just in case for now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be useful to leave a comment, e.g.

// Possibly this is never needed and could/should be removed (see #5658).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did but that might be a better way to put it

@rwgk
Copy link
Collaborator

rwgk commented May 13, 2025

Pulled from #5646

What do you think about also pulling in this (ci.yml)?

-        runs-on: [ubuntu-22.04, windows-2022, macos-13]
+        runs-on: [ubuntu-24.04, windows-2022, macos-13]

@henryiii
Copy link
Collaborator Author

It's in #5657.

Signed-off-by: Henry Schreiner <[email protected]>
@henryiii henryiii force-pushed the henryiii/fix/updatedict branch 2 times, most recently from b3717d0 to 52a37c1 Compare May 13, 2025 15:48
@henryiii henryiii force-pushed the henryiii/fix/updatedict branch from 52a37c1 to 3626c26 Compare May 13, 2025 15:49
@henryiii henryiii merged commit 853bafa into pybind:master May 13, 2025
65 checks passed
@henryiii henryiii deleted the henryiii/fix/updatedict branch May 13, 2025 18:09
@github-actions github-actions bot added the needs changelog Possibly needs a changelog entry label May 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs changelog Possibly needs a changelog entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants