https://github.com/python/cpython/commit/8f59fbb082a4d64619aeededc47b3b45212d2341
commit: 8f59fbb082a4d64619aeededc47b3b45212d2341
branch: main
author: Peter Bierma <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2025-07-20T20:49:00+02:00
summary:

gh-136492: Add `FrameLocalsProxyType` to `types` (GH-136546)

Co-authored-by: Jelle Zijlstra <[email protected]>
Co-authored-by: Adam Turner <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst
M Doc/library/types.rst
M Doc/whatsnew/3.15.rst
M Lib/test/test_inspect/test_inspect.py
M Lib/test/test_types.py
M Lib/types.py
M Modules/_typesmodule.c
M Objects/frameobject.c

diff --git a/Doc/library/types.rst b/Doc/library/types.rst
index 2bedd7fdd3c8c8..207024a7619902 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 <traceback.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 7e47fa263d9a5e..4d4fb77ad4f030 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 4f3983d83c7c06..30e01b8cd87a75 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -5786,6 +5786,7 @@ def test_types_module_has_signatures(self):
             '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 fccdcc975e6c43..9b0ae709d7968d 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -53,6 +53,7 @@ def test_names(self):
             '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 @@ def call(part):
         """
         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 cf0549315a7814..f96c75b46daba7 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -58,7 +58,10 @@ def _m(self): pass
         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 00000000000000..7ab5b068a7f691
--- /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 a30a88196e7192..df6b4c93cb87a6 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 601fc69c4b1f60..97de1e06efe1b2 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 *

_______________________________________________
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]

Reply via email to