Skip to content

Commit 7b46ae9

Browse files
[3.13] gh-125608: Trigger dictionary watchers when inline values change (GH-125611) (GH-125982)
Dictionary watchers on an object's attributes dictionary (`object.__dict__`) were not triggered when the managed dictionary used the object's inline values. (cherry picked from commit 5989eb7) Co-authored-by: Sam Gross <[email protected]>
1 parent dba992b commit 7b46ae9

File tree

3 files changed

+35
-6
lines changed

3 files changed

+35
-6
lines changed

Lib/test/test_capi/test_watchers.py

+17
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,23 @@ def test_dealloc(self):
9696
del d
9797
self.assert_events(["dealloc"])
9898

99+
def test_object_dict(self):
100+
class MyObj: pass
101+
o = MyObj()
102+
103+
with self.watcher() as wid:
104+
self.watch(wid, o.__dict__)
105+
o.foo = "bar"
106+
o.foo = "baz"
107+
del o.foo
108+
self.assert_events(["new:foo:bar", "mod:foo:baz", "del:foo"])
109+
110+
with self.watcher() as wid:
111+
self.watch(wid, o.__dict__)
112+
for _ in range(100):
113+
o.foo = "bar"
114+
self.assert_events(["new:foo:bar"] + ["mod:foo:bar"] * 99)
115+
99116
def test_unwatch(self):
100117
d = {}
101118
with self.watcher() as wid:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a bug where dictionary watchers (e.g., :c:func:`PyDict_Watch`) on an
2+
object's attribute dictionary (:attr:`~object.__dict__`) were not triggered
3+
when the object's attributes were modified.

Objects/dictobject.c

+15-6
Original file line numberDiff line numberDiff line change
@@ -6825,15 +6825,24 @@ store_instance_attr_lock_held(PyObject *obj, PyDictValues *values,
68256825
}
68266826

68276827
PyObject *old_value = values->values[ix];
6828+
if (old_value == NULL && value == NULL) {
6829+
PyErr_Format(PyExc_AttributeError,
6830+
"'%.100s' object has no attribute '%U'",
6831+
Py_TYPE(obj)->tp_name, name);
6832+
return -1;
6833+
}
6834+
6835+
if (dict) {
6836+
PyInterpreterState *interp = _PyInterpreterState_GET();
6837+
PyDict_WatchEvent event = (old_value == NULL ? PyDict_EVENT_ADDED :
6838+
value == NULL ? PyDict_EVENT_DELETED :
6839+
PyDict_EVENT_MODIFIED);
6840+
_PyDict_NotifyEvent(interp, event, dict, name, value);
6841+
}
6842+
68286843
FT_ATOMIC_STORE_PTR_RELEASE(values->values[ix], Py_XNewRef(value));
68296844

68306845
if (old_value == NULL) {
6831-
if (value == NULL) {
6832-
PyErr_Format(PyExc_AttributeError,
6833-
"'%.100s' object has no attribute '%U'",
6834-
Py_TYPE(obj)->tp_name, name);
6835-
return -1;
6836-
}
68376846
_PyDictValues_AddToInsertionOrder(values, ix);
68386847
if (dict) {
68396848
assert(dict->ma_values == values);

0 commit comments

Comments
 (0)