From 78b842c03b78bec6d21ac2abd4694ede0a3d6835 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:12:33 -0400 Subject: [PATCH 01/16] Expose FrameLocalsProxy in types and _types --- Lib/types.py | 1 + Modules/_typesmodule.c | 1 + 2 files changed, 2 insertions(+) diff --git a/Lib/types.py b/Lib/types.py index cf0549315a7814..a4189ac4f9fae5 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -69,6 +69,7 @@ def _m(self): pass EllipsisType = type(Ellipsis) NoneType = type(None) NotImplementedType = type(NotImplemented) + FrameLocalsProxy = (lambda: type(sys._getframe().f_locals)() # CapsuleType cannot be accessed from pure Python, # so there is no fallback definition. diff --git a/Modules/_typesmodule.c b/Modules/_typesmodule.c index a30a88196e7192..60ea2fda5ed04f 100644 --- a/Modules/_typesmodule.c +++ b/Modules/_typesmodule.c @@ -46,6 +46,7 @@ _types_exec(PyObject *m) EXPORT_STATIC_TYPE("TracebackType", PyTraceBack_Type); EXPORT_STATIC_TYPE("UnionType", _PyUnion_Type); EXPORT_STATIC_TYPE("WrapperDescriptorType", PyWrapperDescr_Type); + EXPORT_STATIC_TYPE("FrameLocalsProxy", PyFrameLocalsProxy_Type); #undef EXPORT_STATIC_TYPE return 0; } From e2ddcbec09fb7270183ad2e203550339c2065e79 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:16:59 -0400 Subject: [PATCH 02/16] Add a test case. --- Lib/test/test_types.py | 5 +++++ Lib/types.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index fccdcc975e6c43..149aac7424cfc2 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -711,6 +711,11 @@ def call(part): """ assert_python_ok("-c", code) + def test_frame_locals_proxy(self): + frame = inspect.currentframe() + self.assertIsNotNone(frame) + self.assertIsInstance(frame.f_locals, types.FrameLocalsProxy) + class UnionTests(unittest.TestCase): diff --git a/Lib/types.py b/Lib/types.py index a4189ac4f9fae5..30932b1f6021f8 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -69,7 +69,7 @@ def _m(self): pass EllipsisType = type(Ellipsis) NoneType = type(None) NotImplementedType = type(NotImplemented) - FrameLocalsProxy = (lambda: type(sys._getframe().f_locals)() + FrameLocalsProxy = (lambda: type(sys._getframe().f_locals))() # CapsuleType cannot be accessed from pure Python, # so there is no fallback definition. From 22e056dbfb8f328c79fa23b85679d2874d80b241 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:20:35 -0400 Subject: [PATCH 03/16] Add a documentation entry. --- Doc/library/types.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 2bedd7fdd3c8c8..927ea5b044f897 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -333,6 +333,14 @@ Standard names are defined for the following types: :attr:`tb.tb_frame ` if ``tb`` is a traceback object. +.. data:: FrameLocalsProxyType + + The type of frame :func:`locals` proxy objects, as found on the + :attr:`frame.f_locals` attribute. See :pep:`667` for more information. + + .. versionadded:: next + + .. data:: GetSetDescriptorType The type of objects defined in extension modules with ``PyGetSetDef``, such From b8fd7330fe1aa2062efbc58f4380bf79da8c8d44 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:21:04 -0400 Subject: [PATCH 04/16] Use the 'type' suffix. --- Lib/types.py | 2 +- Modules/_typesmodule.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/types.py b/Lib/types.py index 30932b1f6021f8..7fc4db13eeaa54 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -69,7 +69,7 @@ def _m(self): pass EllipsisType = type(Ellipsis) NoneType = type(None) NotImplementedType = type(NotImplemented) - FrameLocalsProxy = (lambda: type(sys._getframe().f_locals))() + FrameLocalsProxyType = (lambda: type(sys._getframe().f_locals))() # CapsuleType cannot be accessed from pure Python, # so there is no fallback definition. diff --git a/Modules/_typesmodule.c b/Modules/_typesmodule.c index 60ea2fda5ed04f..d2e941f4677503 100644 --- a/Modules/_typesmodule.c +++ b/Modules/_typesmodule.c @@ -46,7 +46,7 @@ _types_exec(PyObject *m) EXPORT_STATIC_TYPE("TracebackType", PyTraceBack_Type); EXPORT_STATIC_TYPE("UnionType", _PyUnion_Type); EXPORT_STATIC_TYPE("WrapperDescriptorType", PyWrapperDescr_Type); - EXPORT_STATIC_TYPE("FrameLocalsProxy", PyFrameLocalsProxy_Type); + EXPORT_STATIC_TYPE("FrameLocalsProxyType", PyFrameLocalsProxy_Type); #undef EXPORT_STATIC_TYPE return 0; } From abbe1d36732d4a1b264d5f6576aab2edc3defd95 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:22:44 -0400 Subject: [PATCH 05/16] Add a whatsnew entry. --- Doc/whatsnew/3.15.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index e9b88458acdd79..9aec3a4d8a1c8c 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -226,6 +226,13 @@ os.path (Contributed by Petr Viktorin for :cve:`2025-4517`.) +types +------ + +* Added :data:`types.FrameLocalsProxyType` for representing the type of + the :attr:`frame.f_locals` attribute, as determined in :pep:`667`. + + shelve ------ From 753c9bc21fea5f7c325bedf4b6bb91319d226308 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:23:50 -0400 Subject: [PATCH 06/16] Add blurb. --- .../next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst 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..41114c781b2193 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-11-10-23-44.gh-issue-136492.BVi5h0.rst @@ -0,0 +1 @@ +Add :data:`~types.FrameLocalsProxyType` to the :mod:`types` module. From b087d9b9e4ebe35e38a008cb3625f6a7fb4c0e15 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:24:50 -0400 Subject: [PATCH 07/16] Fix the tests. --- Lib/test/test_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 149aac7424cfc2..bb57d7e1e63aa9 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -57,7 +57,7 @@ def test_names(self): 'LambdaType', 'MappingProxyType', 'MemberDescriptorType', 'MethodDescriptorType', 'MethodType', 'MethodWrapperType', 'ModuleType', 'NoneType', 'NotImplementedType', 'SimpleNamespace', - 'TracebackType', 'UnionType', 'WrapperDescriptorType', + 'TracebackType', 'UnionType', 'WrapperDescriptorType', 'FrameLocalsProxyType' } self.assertEqual(all_names, set(c_types.__all__)) self.assertEqual(all_names - c_only_names, set(py_types.__all__)) @@ -714,7 +714,7 @@ def call(part): def test_frame_locals_proxy(self): frame = inspect.currentframe() self.assertIsNotNone(frame) - self.assertIsInstance(frame.f_locals, types.FrameLocalsProxy) + self.assertIsInstance(frame.f_locals, types.FrameLocalsProxyType) class UnionTests(unittest.TestCase): From 1971a14fc8ff68fa74455b727521d7c060346ae1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:25:40 -0400 Subject: [PATCH 08/16] Sort the name alphabetically. --- Lib/test/test_types.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index bb57d7e1e63aa9..bfa23899521251 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -53,11 +53,12 @@ def test_names(self): 'AsyncGeneratorType', 'BuiltinFunctionType', 'BuiltinMethodType', 'CapsuleType', 'CellType', 'ClassMethodDescriptorType', 'CodeType', 'CoroutineType', 'EllipsisType', 'FrameType', 'FunctionType', - 'GeneratorType', 'GenericAlias', 'GetSetDescriptorType', - 'LambdaType', 'MappingProxyType', 'MemberDescriptorType', - 'MethodDescriptorType', 'MethodType', 'MethodWrapperType', - 'ModuleType', 'NoneType', 'NotImplementedType', 'SimpleNamespace', - 'TracebackType', 'UnionType', 'WrapperDescriptorType', 'FrameLocalsProxyType' + 'FrameLocalsProxyType', 'GeneratorType', 'GenericAlias', + 'GetSetDescriptorType', 'LambdaType', 'MappingProxyType', + 'MemberDescriptorType', 'MethodDescriptorType', 'MethodType', + 'MethodWrapperType', 'ModuleType', 'NoneType', 'NotImplementedType', + 'SimpleNamespace', 'TracebackType', 'UnionType', + 'WrapperDescriptorType', } self.assertEqual(all_names, set(c_types.__all__)) self.assertEqual(all_names - c_only_names, set(py_types.__all__)) From fdeb2d084b39f88b92da0dcfddc52f256b16799c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 10:28:57 -0400 Subject: [PATCH 09/16] Sort alphabetically in _types. --- Modules/_typesmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_typesmodule.c b/Modules/_typesmodule.c index d2e941f4677503..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); @@ -46,7 +47,6 @@ _types_exec(PyObject *m) EXPORT_STATIC_TYPE("TracebackType", PyTraceBack_Type); EXPORT_STATIC_TYPE("UnionType", _PyUnion_Type); EXPORT_STATIC_TYPE("WrapperDescriptorType", PyWrapperDescr_Type); - EXPORT_STATIC_TYPE("FrameLocalsProxyType", PyFrameLocalsProxy_Type); #undef EXPORT_STATIC_TYPE return 0; } From c8600b9cde1cbd70a89a77f7dcef459dd65cde8e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 11:08:29 -0400 Subject: [PATCH 10/16] Move whatsnew section. --- Doc/whatsnew/3.15.rst | 14 +++++++------- Lib/test/test_inspect/test_inspect.py | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 9aec3a4d8a1c8c..c086dd85d14174 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -226,13 +226,6 @@ os.path (Contributed by Petr Viktorin for :cve:`2025-4517`.) -types ------- - -* Added :data:`types.FrameLocalsProxyType` for representing the type of - the :attr:`frame.f_locals` attribute, as determined in :pep:`667`. - - shelve ------ @@ -285,6 +278,13 @@ tarfile and :cve:`2025-4435`.) +types +------ + +* Added :data:`types.FrameLocalsProxyType` for representing the type of + the :attr:`frame.f_locals` attribute, as determined in :pep:`667`. + + zlib ---- diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 0ea029b977b3fc..2ce7e15a10210a 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'} } self._test_module_has_signatures(types, unsupported_signature=unsupported_signature, From 70cc4db81c0417dcc491832956e8832be3dd1256 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 11:12:36 -0400 Subject: [PATCH 11/16] Somewhat fix the tests. --- Lib/test/test_inspect/test_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 2ce7e15a10210a..9d15a982b36e8c 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5786,7 +5786,7 @@ def test_types_module_has_signatures(self): 'AsyncGeneratorType': {'athrow'}, 'CoroutineType': {'throw'}, 'GeneratorType': {'throw'}, - 'FrameLocalsProxyType': {'setdefault', 'pop'} + 'FrameLocalsProxyType': {'setdefault', 'pop', 'get'} } self._test_module_has_signatures(types, unsupported_signature=unsupported_signature, From fee5256998e7a5cd6fca80fc0b3c354d16e34d9f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 11:12:54 -0400 Subject: [PATCH 12/16] Reduce the diff. --- Lib/test/test_types.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index bfa23899521251..3dd753b0e67400 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -53,12 +53,12 @@ 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', 'ModuleType', 'NoneType', 'NotImplementedType', - 'SimpleNamespace', 'TracebackType', 'UnionType', - 'WrapperDescriptorType', + 'GeneratorType', 'GenericAlias', 'GetSetDescriptorType', + 'LambdaType', 'MappingProxyType', 'MemberDescriptorType', + 'MethodDescriptorType', 'MethodType', 'MethodWrapperType', + 'ModuleType', 'NoneType', 'NotImplementedType', 'SimpleNamespace', + 'TracebackType', 'UnionType', 'WrapperDescriptorType', + 'FrameLocalsProxyType' } self.assertEqual(all_names, set(c_types.__all__)) self.assertEqual(all_names - c_only_names, set(py_types.__all__)) From a110148c9c93efa9d9f56cc8df48714c9c7281c5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 11:21:50 -0400 Subject: [PATCH 13/16] Use a workaround for the tests. --- Lib/test/test_inspect/test_inspect.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 9d15a982b36e8c..a4c9f474a39891 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5788,9 +5788,11 @@ def test_types_module_has_signatures(self): 'GeneratorType': {'throw'}, 'FrameLocalsProxyType': {'setdefault', 'pop', 'get'} } + no_signature = {'FrameLocalsProxyType'} self._test_module_has_signatures(types, unsupported_signature=unsupported_signature, - methods_no_signature=methods_no_signature) + methods_no_signature=methods_no_signature, + no_signature=no_signature) def test_sys_module_has_signatures(self): no_signature = {'getsizeof', 'set_asyncgen_hooks'} From 929f81e4f36c9ed42b909e2450d76c5d0df317fe Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 11 Jul 2025 15:40:08 -0400 Subject: [PATCH 14/16] Move PEP note and remove locals() reference. --- Doc/library/types.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 927ea5b044f897..772a2ae88da75c 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -335,11 +335,14 @@ Standard names are defined for the following types: .. data:: FrameLocalsProxyType - The type of frame :func:`locals` proxy objects, as found on the - :attr:`frame.f_locals` attribute. See :pep:`667` for more information. + The type of frame locals proxy objects, as found on the + :attr:`frame.f_locals` attribute. .. versionadded:: next + .. seealso:: + :pep:`667` + .. data:: GetSetDescriptorType From fb710ebe45f971080d2485fc2106d4d25bf15697 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 13:57:30 -0400 Subject: [PATCH 15/16] Update Doc/whatsnew/3.15.rst Co-authored-by: Jelle Zijlstra --- Doc/whatsnew/3.15.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index c086dd85d14174..4680791571b577 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -282,7 +282,7 @@ types ------ * Added :data:`types.FrameLocalsProxyType` for representing the type of - the :attr:`frame.f_locals` attribute, as determined in :pep:`667`. + the :attr:`frame.f_locals` attribute, as described in :pep:`667`. zlib From 592326356282c4b2b75162f3ce2b896fc03b4c39 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 12 Jul 2025 16:58:04 -0400 Subject: [PATCH 16/16] Add a signature for FrameLocalsProxy() --- Lib/test/test_inspect/test_inspect.py | 4 +--- Objects/frameobject.c | 10 ++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index a4c9f474a39891..9d15a982b36e8c 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5788,11 +5788,9 @@ def test_types_module_has_signatures(self): 'GeneratorType': {'throw'}, 'FrameLocalsProxyType': {'setdefault', 'pop', 'get'} } - no_signature = {'FrameLocalsProxyType'} self._test_module_has_signatures(types, unsupported_signature=unsupported_signature, - methods_no_signature=methods_no_signature, - no_signature=no_signature) + methods_no_signature=methods_no_signature) def test_sys_module_has_signatures(self): no_signature = {'getsizeof', 'set_asyncgen_hooks'} diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 601fc69c4b1f60..b1f07b78d28a35 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 *