-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
fix: use original dict #5658
Conversation
include/pybind11/detail/init.h
Outdated
@@ -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__"); |
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.
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 withtp_dictoffset
, and even then it may benullptr
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__")
raisesAttributeError
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.
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.
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.
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.
Might be useful to leave a comment, e.g.
// Possibly this is never needed and could/should be removed (see #5658).
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.
I did but that might be a better way to put it
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] |
It's in #5657. |
Signed-off-by: Henry Schreiner <[email protected]>
b3717d0
to
52a37c1
Compare
Signed-off-by: Henry Schreiner <[email protected]>
52a37c1
to
3626c26
Compare
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-6b387013ea09Suggested changelog entry: