Skip to content

Commit 8f59fbb

Browse files
ZeroIntensityJelleZijlstraAA-Turner
authored
gh-136492: Add FrameLocalsProxyType to types (GH-136546)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
1 parent e24c66d commit 8f59fbb

File tree

8 files changed

+47
-1
lines changed

8 files changed

+47
-1
lines changed

Doc/library/types.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,16 @@ Standard names are defined for the following types:
333333
:attr:`tb.tb_frame <traceback.tb_frame>` if ``tb`` is a traceback object.
334334

335335

336+
.. data:: FrameLocalsProxyType
337+
338+
The type of frame locals proxy objects, as found on the
339+
:attr:`frame.f_locals` attribute.
340+
341+
.. versionadded:: next
342+
343+
.. seealso:: :pep:`667`
344+
345+
336346
.. data:: GetSetDescriptorType
337347

338348
The type of objects defined in extension modules with ``PyGetSetDef``, such

Doc/whatsnew/3.15.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,15 @@ tarfile
312312
and :cve:`2025-4435`.)
313313

314314

315+
types
316+
------
317+
318+
* Expose the write-through :func:`locals` proxy type
319+
as :data:`types.FrameLocalsProxyType`.
320+
This represents the type of the :attr:`frame.f_locals` attribute,
321+
as described in :pep:`667`.
322+
323+
315324
unittest
316325
--------
317326

Lib/test/test_inspect/test_inspect.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5786,6 +5786,7 @@ def test_types_module_has_signatures(self):
57865786
'AsyncGeneratorType': {'athrow'},
57875787
'CoroutineType': {'throw'},
57885788
'GeneratorType': {'throw'},
5789+
'FrameLocalsProxyType': {'setdefault', 'pop', 'get'},
57895790
}
57905791
self._test_module_has_signatures(types,
57915792
unsupported_signature=unsupported_signature,

Lib/test/test_types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def test_names(self):
5353
'AsyncGeneratorType', 'BuiltinFunctionType', 'BuiltinMethodType',
5454
'CapsuleType', 'CellType', 'ClassMethodDescriptorType', 'CodeType',
5555
'CoroutineType', 'EllipsisType', 'FrameType', 'FunctionType',
56+
'FrameLocalsProxyType',
5657
'GeneratorType', 'GenericAlias', 'GetSetDescriptorType',
5758
'LambdaType', 'MappingProxyType', 'MemberDescriptorType',
5859
'MethodDescriptorType', 'MethodType', 'MethodWrapperType',
@@ -711,6 +712,16 @@ def call(part):
711712
"""
712713
assert_python_ok("-c", code)
713714

715+
def test_frame_locals_proxy_type(self):
716+
self.assertIsInstance(types.FrameLocalsProxyType, type)
717+
self.assertIsInstance(types.FrameLocalsProxyType.__doc__, str)
718+
self.assertEqual(types.FrameLocalsProxyType.__module__, 'builtins')
719+
self.assertEqual(types.FrameLocalsProxyType.__name__, 'FrameLocalsProxy')
720+
721+
frame = inspect.currentframe()
722+
self.assertIsNotNone(frame)
723+
self.assertIsInstance(frame.f_locals, types.FrameLocalsProxyType)
724+
714725

715726
class UnionTests(unittest.TestCase):
716727

Lib/types.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ def _m(self): pass
5858
raise TypeError
5959
except TypeError as exc:
6060
TracebackType = type(exc.__traceback__)
61-
FrameType = type(exc.__traceback__.tb_frame)
61+
62+
_f = (lambda: sys._getframe())()
63+
FrameType = type(_f)
64+
FrameLocalsProxyType = type(_f.f_locals)
6265

6366
GetSetDescriptorType = type(FunctionType.__code__)
6467
MemberDescriptorType = type(FunctionType.__globals__)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose :pep:`667`'s :data:`~types.FrameLocalsProxyType` in the :mod:`types` module.

Modules/_typesmodule.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ _types_exec(PyObject *m)
2828
EXPORT_STATIC_TYPE("CoroutineType", PyCoro_Type);
2929
EXPORT_STATIC_TYPE("EllipsisType", PyEllipsis_Type);
3030
EXPORT_STATIC_TYPE("FrameType", PyFrame_Type);
31+
EXPORT_STATIC_TYPE("FrameLocalsProxyType", PyFrameLocalsProxy_Type);
3132
EXPORT_STATIC_TYPE("FunctionType", PyFunction_Type);
3233
EXPORT_STATIC_TYPE("GeneratorType", PyGen_Type);
3334
EXPORT_STATIC_TYPE("GenericAlias", Py_GenericAliasType);

Objects/frameobject.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,15 @@ static PyMethodDef framelocalsproxy_methods[] = {
913913
{NULL, NULL} /* sentinel */
914914
};
915915

916+
PyDoc_STRVAR(framelocalsproxy_doc,
917+
"FrameLocalsProxy($frame)\n"
918+
"--\n"
919+
"\n"
920+
"Create a write-through view of the locals dictionary for a frame.\n"
921+
"\n"
922+
" frame\n"
923+
" the frame object to wrap.");
924+
916925
PyTypeObject PyFrameLocalsProxy_Type = {
917926
PyVarObject_HEAD_INIT(&PyType_Type, 0)
918927
.tp_name = "FrameLocalsProxy",
@@ -933,6 +942,7 @@ PyTypeObject PyFrameLocalsProxy_Type = {
933942
.tp_alloc = PyType_GenericAlloc,
934943
.tp_new = framelocalsproxy_new,
935944
.tp_free = PyObject_GC_Del,
945+
.tp_doc = framelocalsproxy_doc,
936946
};
937947

938948
PyObject *

0 commit comments

Comments
 (0)