diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index a0a11966ebf3c1..f634d30a338219 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -557,6 +557,47 @@ def test_as_dict(self): with self.assertRaises(KeyError): d['non_exist'] + def test_values_reference_ownership(self): + class Value: + pass + + for duplicate_names in (False, True): + with self.subTest(duplicate_names=duplicate_names): + def make_frame(first, second): + return sys._getframe() + + if duplicate_names: + make_frame.__code__ = make_frame.__code__.replace( + co_varnames=('value', 'value')) + + first = Value() + second = Value() + extra = Value() + refs = [weakref.ref(value) for value in (first, second, extra)] + frame = make_frame(first, second) + proxy = frame.f_locals + proxy['extra'] = extra + values = proxy.values() + if duplicate_names: + self.assertEqual(values, [first, extra]) + else: + self.assertEqual(values, [first, second, extra]) + + frame.clear() + del first, second, extra + support.gc_collect() + self.assertIsNotNone(refs[0]()) + self.assertIsNotNone(refs[2]()) + if duplicate_names: + self.assertIsNone(refs[1]()) + else: + self.assertIsNotNone(refs[1]()) + + values.clear() + support.gc_collect() + for ref in refs: + self.assertIsNone(ref()) + def test_as_number(self): x = 1 y = 2 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-10-40-00.gh-issue-150942.mK9qLs.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-10-40-00.gh-issue-150942.mK9qLs.rst new file mode 100644 index 00000000000000..d1351279c757ed --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-10-40-00.gh-issue-150942.mK9qLs.rst @@ -0,0 +1,2 @@ +Avoid redundant reference count updates in the ``values()`` method of the +:attr:`frame.f_locals` proxy for optimized scopes. diff --git a/Objects/frameobject.c b/Objects/frameobject.c index a4cc14a6eaad45..360af5800ae229 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -634,14 +634,15 @@ framelocalsproxy_values(PyObject *self, PyObject *Py_UNUSED(ignored)) PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i); int first = framelocalsproxy_is_first_occurrence(seen, name); if (first == 1) { - if (PyList_Append(values, value) < 0) { - Py_DECREF(value); + if (_PyList_AppendTakeRef((PyListObject *)values, value) < 0) { goto error; } } - Py_DECREF(value); - if (first < 0) { - goto error; + else { + Py_DECREF(value); + if (first < 0) { + goto error; + } } } }