https://github.com/python/cpython/commit/5989eb74463c26780632f17f221d6bf4c9372a01
commit: 5989eb74463c26780632f17f221d6bf4c9372a01
branch: main
author: Sam Gross <[email protected]>
committer: colesbury <[email protected]>
date: 2024-10-21T08:23:38-04:00
summary:
gh-125608: Trigger dictionary watchers when inline values change (#125611)
Dictionary watchers on an object's attributes dictionary
(`object.__dict__`) were not triggered when the managed dictionary used
the object's inline values.
files:
A Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst
M Lib/test/test_capi/test_watchers.py
M Objects/dictobject.c
diff --git a/Lib/test/test_capi/test_watchers.py
b/Lib/test/test_capi/test_watchers.py
index 4bb764bf9d0963..e578a622a03487 100644
--- a/Lib/test/test_capi/test_watchers.py
+++ b/Lib/test/test_capi/test_watchers.py
@@ -97,6 +97,23 @@ def test_dealloc(self):
del d
self.assert_events(["dealloc"])
+ def test_object_dict(self):
+ class MyObj: pass
+ o = MyObj()
+
+ with self.watcher() as wid:
+ self.watch(wid, o.__dict__)
+ o.foo = "bar"
+ o.foo = "baz"
+ del o.foo
+ self.assert_events(["new:foo:bar", "mod:foo:baz", "del:foo"])
+
+ with self.watcher() as wid:
+ self.watch(wid, o.__dict__)
+ for _ in range(100):
+ o.foo = "bar"
+ self.assert_events(["new:foo:bar"] + ["mod:foo:bar"] * 99)
+
def test_unwatch(self):
d = {}
with self.watcher() as wid:
diff --git
a/Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst
b/Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst
new file mode 100644
index 00000000000000..e70f9f173957a2
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst
@@ -0,0 +1,3 @@
+Fix a bug where dictionary watchers (e.g., :c:func:`PyDict_Watch`) on an
+object's attribute dictionary (:attr:`~object.__dict__`) were not triggered
+when the object's attributes were modified.
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index b27599d2815c82..806096f5814062 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -6835,15 +6835,24 @@ store_instance_attr_lock_held(PyObject *obj,
PyDictValues *values,
}
PyObject *old_value = values->values[ix];
+ if (old_value == NULL && value == NULL) {
+ PyErr_Format(PyExc_AttributeError,
+ "'%.100s' object has no attribute '%U'",
+ Py_TYPE(obj)->tp_name, name);
+ return -1;
+ }
+
+ if (dict) {
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ PyDict_WatchEvent event = (old_value == NULL ? PyDict_EVENT_ADDED :
+ value == NULL ? PyDict_EVENT_DELETED :
+ PyDict_EVENT_MODIFIED);
+ _PyDict_NotifyEvent(interp, event, dict, name, value);
+ }
+
FT_ATOMIC_STORE_PTR_RELEASE(values->values[ix], Py_XNewRef(value));
if (old_value == NULL) {
- if (value == NULL) {
- PyErr_Format(PyExc_AttributeError,
- "'%.100s' object has no attribute '%U'",
- Py_TYPE(obj)->tp_name, name);
- return -1;
- }
_PyDictValues_AddToInsertionOrder(values, ix);
if (dict) {
assert(dict->ma_values == values);
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]