diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -1,5 +1,6 @@ import gc import sys +import types import unittest import weakref @@ -109,6 +110,57 @@ class ClearTest(unittest.TestCase): self.assertIs(None, wr()) +class FrameLocalsTest(unittest.TestCase): + """ + Tests for the .f_locals attribute. + """ + + def make_frames(self): + def outer(): + x = 5 + y = 6 + def inner(): + z = x + 2 + 1/0 + t = 9 + return inner() + try: + outer() + except ZeroDivisionError as e: + tb = e.__traceback__ + frames = [] + while tb: + frames.append(tb.tb_frame) + tb = tb.tb_next + return frames + + def test_locals(self): + f, outer, inner = self.make_frames() + outer_locals = outer.f_locals + self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType) + self.assertEqual(outer_locals, {'x': 5, 'y': 6}) + inner_locals = inner.f_locals + self.assertEqual(inner_locals, {'x': 5, 'z': 7}) + + def test_clear_locals(self): + # Test f_locals after clear() (issue #21897) + f, outer, inner = self.make_frames() + outer.clear() + inner.clear() + self.assertEqual(outer.f_locals, {}) + self.assertEqual(inner.f_locals, {}) + + def test_locals_clear_locals(self): + # Test f_locals before and after clear() (to exercise caching) + f, outer, inner = self.make_frames() + outer.f_locals + inner.f_locals + outer.clear() + inner.clear() + self.assertEqual(outer.f_locals, {}) + self.assertEqual(inner.f_locals, {}) + + def test_main(): support.run_unittest(__name__) diff --git a/Objects/frameobject.c b/Objects/frameobject.c --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -786,7 +786,7 @@ map_to_dict(PyObject *map, Py_ssize_t nm PyObject *key = PyTuple_GET_ITEM(map, j); PyObject *value = values[j]; assert(PyUnicode_Check(key)); - if (deref) { + if (deref && value != NULL) { assert(PyCell_Check(value)); value = PyCell_GET(value); }