Skip to content

gh-136492: Add FrameLocalsProxy to types #136546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Doc/library/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,17 @@ 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
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,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 described in :pep:`667`.


zlib
----

Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -5786,6 +5786,7 @@ def test_types_module_has_signatures(self):
'AsyncGeneratorType': {'athrow'},
'CoroutineType': {'throw'},
'GeneratorType': {'throw'},
'FrameLocalsProxyType': {'setdefault', 'pop', 'get'}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fixable by converting these methods to Argument Clinic or manually adding a docstring with a signature.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear I'm not asking you to necessarily fix this, though I think it'd be nice to reduce the size of the denylist in this test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we should fix these, but would you like it to be done in this PR? Same goes for the tp_doc on FrameLocalsProxy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you! I feel it makes sense though that if we expose this publicly, we should also give it a docstring.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add the tp_doc in this PR, but I think the AC changes would be better for a follow-up.

}
self._test_module_has_signatures(types,
unsupported_signature=unsupported_signature,
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def test_names(self):
'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__))
Expand Down Expand Up @@ -711,6 +712,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.FrameLocalsProxyType)


class UnionTests(unittest.TestCase):

Expand Down
1 change: 1 addition & 0 deletions Lib/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def _m(self): pass
EllipsisType = type(Ellipsis)
NoneType = type(None)
NotImplementedType = type(NotImplemented)
FrameLocalsProxyType = (lambda: type(sys._getframe().f_locals))()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introspecting this class currently would make you think it is builtins.FrameLocalProxy:

>>> FrameLocalsProxyType = (lambda: type(sys._getframe().f_locals))()
>>> FrameLocalsProxyType.__module__
'builtins'
>>> FrameLocalsProxyType.__name__
'FrameLocalsProxy'

What do you think about changing its tp_name to types.FrameLocalsProxyType, so that the runtime __module__ and __name__ attributes are consistent with the place where you can actually find it? This is what types.SimpleNamespace already does.

Personally I'd ideally want to do this for all the types.* types, but there are compatibility concerns for some. Here though, given that the type is relatively new and private, maybe we can get away with changing its name and module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me, any objections @gaogaotiantian?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean if we are keeping the behavior of almost all the types in types, the new "correct" one would be the confusing one. FrameType has the name frame, CellType has the name cell, CodeType has the name code. The closest one MappingProxyType even has the name mappingproxy. I think if this is not super critical, maybe sticking to the old tradition is more consistent to users.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the fact that the __module__ is inconsistent on so many existing types shouldn't be an argument to keep doing it wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with __module__, my argument was more about __name__.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that __module__ is set from the tp_name. A compromise could be to name it types.FrameLocalsProxy, but that deviates from the *Type convention that currently exists in types.


# CapsuleType cannot be accessed from pure Python,
# so there is no fallback definition.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :data:`~types.FrameLocalsProxyType` to the :mod:`types` module.
1 change: 1 addition & 0 deletions Modules/_typesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 *
Expand Down
Loading