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 13 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
8 changes: 8 additions & 0 deletions Doc/library/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,14 @@ 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 :func:`locals` proxy objects, as found on the
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's proper to link :func:`locals` here. locals() is a function that returns a snapshot (dict) of the local variables. That is a different path to local variables. frame.f_locals is designed to be different than locals(), it might be a bit confusing here.

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 just used it to give context to what "locals" mean. I did the same for the C API docs. Should we change it there too?

:attr:`frame.f_locals` attribute. See :pep:`667` for more information.
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 not sure if we should refer to PEP 667 here - the PEP itself is a historical document and we are redirecting people to a different place in the PEP.

Copy link
Member Author

Choose a reason for hiding this comment

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

How about a seealso note instead?


.. versionadded:: next


.. 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 determined in :pep:`667`.


zlib
----

Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -5786,10 +5786,13 @@ 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.

}
no_signature = {'FrameLocalsProxyType'}
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 adding a tp_doc to FrameLocalsProxy that includes the signature; see types.CellType in cellobject.c for an example.

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'}
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__.


# 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
Loading