From 8f59fbb082a4d64619aeededc47b3b45212d2341 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 20 Jul 2025 20:49:00 +0200 Subject: [PATCH] gh-136492: Add `FrameLocalsProxyType` to `types` (GH-136546) Co-authored-by: Jelle Zijlstra Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/types.rst | 10 ++++++++++ Doc/whatsnew/3.15.rst | 9 +++++++++ Lib/test/test_inspect/test_inspect.py | 1 + Lib/test/test_types.py | 11 +++++++++++ Lib/types.py | 5 ++++- .../2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst | 1 + Modules/_typesmodule.c | 1 + Objects/frameobject.c | 10 ++++++++++ 8 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 2bedd7fdd3c8..207024a76199 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -333,6 +333,16 @@ Standard names are defined for the following types: :attr:`tb.tb_frame ` if ``tb`` is a traceback object. +.. data:: FrameLocalsProxyType + + The type of frame locals proxy objects, as found on the + :attr:`frame.f_locals` attribute. + + .. versionadded:: next + + .. seealso:: :pep:`667` + + .. data:: GetSetDescriptorType The type of objects defined in extension modules with ``PyGetSetDef``, such diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 7e47fa263d9a..4d4fb77ad4f0 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -312,6 +312,15 @@ tarfile and :cve:`2025-4435`.) +types +------ + +* Expose the write-through :func:`locals` proxy type + as :data:`types.FrameLocalsProxyType`. + This represents the type of the :attr:`frame.f_locals` attribute, + as described in :pep:`667`. + + unittest -------- diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 4f3983d83c7c..30e01b8cd87a 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5786,6 +5786,7 @@ class TestSignatureDefinitions(unittest.TestCase): 'AsyncGeneratorType': {'athrow'}, 'CoroutineType': {'throw'}, 'GeneratorType': {'throw'}, + 'FrameLocalsProxyType': {'setdefault', 'pop', 'get'}, } self._test_module_has_signatures(types, unsupported_signature=unsupported_signature, diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index fccdcc975e6c..9b0ae709d796 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -53,6 +53,7 @@ class TypesTests(unittest.TestCase): 'AsyncGeneratorType', 'BuiltinFunctionType', 'BuiltinMethodType', 'CapsuleType', 'CellType', 'ClassMethodDescriptorType', 'CodeType', 'CoroutineType', 'EllipsisType', 'FrameType', 'FunctionType', + 'FrameLocalsProxyType', 'GeneratorType', 'GenericAlias', 'GetSetDescriptorType', 'LambdaType', 'MappingProxyType', 'MemberDescriptorType', 'MethodDescriptorType', 'MethodType', 'MethodWrapperType', @@ -711,6 +712,16 @@ class TypesTests(unittest.TestCase): """ assert_python_ok("-c", code) + def test_frame_locals_proxy_type(self): + self.assertIsInstance(types.FrameLocalsProxyType, type) + self.assertIsInstance(types.FrameLocalsProxyType.__doc__, str) + self.assertEqual(types.FrameLocalsProxyType.__module__, 'builtins') + self.assertEqual(types.FrameLocalsProxyType.__name__, 'FrameLocalsProxy') + + frame = inspect.currentframe() + self.assertIsNotNone(frame) + self.assertIsInstance(frame.f_locals, types.FrameLocalsProxyType) + class UnionTests(unittest.TestCase): diff --git a/Lib/types.py b/Lib/types.py index cf0549315a78..f96c75b46dab 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -58,7 +58,10 @@ except ImportError: raise TypeError except TypeError as exc: TracebackType = type(exc.__traceback__) - FrameType = type(exc.__traceback__.tb_frame) + + _f = (lambda: sys._getframe())() + FrameType = type(_f) + FrameLocalsProxyType = type(_f.f_locals) GetSetDescriptorType = type(FunctionType.__code__) MemberDescriptorType = type(FunctionType.__globals__) diff --git a/Misc/NEWS.d/next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst b/Misc/NEWS.d/next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst new file mode 100644 index 000000000000..7ab5b068a7f6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst @@ -0,0 +1 @@ +Expose :pep:`667`'s :data:`~types.FrameLocalsProxyType` in the :mod:`types` module. diff --git a/Modules/_typesmodule.c b/Modules/_typesmodule.c index a30a88196e71..df6b4c93cb87 100644 --- a/Modules/_typesmodule.c +++ b/Modules/_typesmodule.c @@ -28,6 +28,7 @@ _types_exec(PyObject *m) EXPORT_STATIC_TYPE("CoroutineType", PyCoro_Type); EXPORT_STATIC_TYPE("EllipsisType", PyEllipsis_Type); EXPORT_STATIC_TYPE("FrameType", PyFrame_Type); + EXPORT_STATIC_TYPE("FrameLocalsProxyType", PyFrameLocalsProxy_Type); EXPORT_STATIC_TYPE("FunctionType", PyFunction_Type); EXPORT_STATIC_TYPE("GeneratorType", PyGen_Type); EXPORT_STATIC_TYPE("GenericAlias", Py_GenericAliasType); diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 601fc69c4b1f..97de1e06efe1 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -913,6 +913,15 @@ static PyMethodDef framelocalsproxy_methods[] = { {NULL, NULL} /* sentinel */ }; +PyDoc_STRVAR(framelocalsproxy_doc, +"FrameLocalsProxy($frame)\n" +"--\n" +"\n" +"Create a write-through view of the locals dictionary for a frame.\n" +"\n" +" frame\n" +" the frame object to wrap."); + PyTypeObject PyFrameLocalsProxy_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "FrameLocalsProxy", @@ -933,6 +942,7 @@ PyTypeObject PyFrameLocalsProxy_Type = { .tp_alloc = PyType_GenericAlloc, .tp_new = framelocalsproxy_new, .tp_free = PyObject_GC_Del, + .tp_doc = framelocalsproxy_doc, }; PyObject * -- 2.47.2