diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index ed8d705da3b0b5..b44b0234d48917 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -817,6 +817,12 @@ function.
.. versionchanged:: 3.10
The *globals*, *locals*, and *eval_str* parameters were added.
+ .. classmethod:: Signature.from_frame(frame)
+
+ Return a :class:`Signature` (or its subclass) object for a given frame object.
+
+ .. versionadded:: 3.13
+
.. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty)
@@ -1107,6 +1113,11 @@ Classes and functions
are the names of the ``*`` and ``**`` arguments or ``None``. *locals* is the
locals dictionary of the given frame.
+ .. deprecated-removed:: 3.13 3.15
+ Use :meth:`Signature.from_frame` instead.
+ For Python version older than 3.13 use
+ `inspect313 `_ PyPI package.
+
.. note::
This function was inadvertently marked as deprecated in Python 3.5.
@@ -1117,6 +1128,11 @@ Classes and functions
:func:`getargvalues`. The format\* arguments are the corresponding optional
formatting functions that are called to turn names and values into strings.
+ .. deprecated-removed:: 3.13 3.15
+ Use :meth:`Signature.from_frame` instead.
+ For Python version older than 3.13 use
+ `inspect313 `_ PyPI package.
+
.. note::
This function was inadvertently marked as deprecated in Python 3.5.
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 51939909000960..bb6c5084c2b466 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -982,6 +982,11 @@ Pending Removal in Python 3.15
and was only useful for Jython support.
(Contributed by Nikita Sobolev in :gh:`116349`.)
+* :func:`inspect.getargvalues` and :func:`inspect.formatargvalues`
+ are deprecated and slated for removal in 3.15;
+ use :meth:`inspect.Signature.from_frame` instead.
+ (Contributed by Nikita Sobolev in :gh:`108901`.)
+
Pending Removal in Python 3.16
------------------------------
@@ -1400,8 +1405,11 @@ CPython bytecode changes
Porting to Python 3.13
======================
-This section lists previously described changes and other bugfixes
-that may require changes to your code.
+* :func:`inspect.getargvalues` and :func:`inspect.formatargvalues`
+ are deprecated. For Python 3.13+ use
+ new :meth:`inspect.Signature.from_frame` API.
+ For older versions, use `inspect313 `_
+ PyPI package which is a backport of this API for Pythons from 3.8 to 3.12.
Changes in the Python API
-------------------------
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 8a2b2c96e993b5..b48d9bfb3eb3f4 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1486,6 +1486,15 @@ def getargvalues(frame):
'args' is a list of the argument names.
'varargs' and 'varkw' are the names of the * and ** arguments or None.
'locals' is the locals dictionary of the given frame."""
+ import warnings
+ warnings._deprecated(
+ "getargvalues",
+ (
+ '{name!r} is deprecated and slated for removal in Python {remove}; '
+ 'use `inspect.Singature.from_frame` instead'
+ ),
+ remove=(3, 15),
+ )
args, varargs, varkw = getargs(frame.f_code)
return ArgInfo(args, varargs, varkw, frame.f_locals)
@@ -1521,6 +1530,15 @@ def formatargvalues(args, varargs, varkw, locals,
next four arguments are the corresponding optional formatting functions
that are called to turn names and values into strings. The ninth
argument is an optional function to format the sequence of arguments."""
+ import warnings
+ warnings._deprecated(
+ "formatargvalues",
+ (
+ '{name!r} is deprecated and slated for removal in Python {remove}; '
+ 'use `inspect.Singature.__str__` instead'
+ ),
+ remove=(3, 15),
+ )
def convert(name, locals=locals,
formatarg=formatarg, formatvalue=formatvalue):
return formatarg(name) + formatvalue(locals[name])
@@ -3093,6 +3111,29 @@ def from_callable(cls, obj, *,
follow_wrapper_chains=follow_wrapped,
globals=globals, locals=locals, eval_str=eval_str)
+ @classmethod
+ def from_frame(cls, frame):
+ """Constructs Signature from a given frame object."""
+ func_code = frame.f_code
+ pos_count = func_code.co_argcount
+ arg_names = func_code.co_varnames
+ keyword_only_count = func_code.co_kwonlyargcount
+
+ defaults = []
+ for name in arg_names[:pos_count]:
+ if frame.f_locals and name in frame.f_locals:
+ defaults.append(frame.f_locals[name])
+
+ kwdefaults = {}
+ for name in arg_names[pos_count : pos_count + keyword_only_count]:
+ if frame.f_locals and name in frame.f_locals:
+ kwdefaults.update({name: frame.f_locals[name]})
+
+ func = types.FunctionType(func_code, {})
+ func.__defaults__ = tuple(defaults)
+ func.__kwdefaults__ = kwdefaults
+ return cls.from_callable(func)
+
@property
def parameters(self):
return self._parameters
diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py
index 52cf68b93b85fa..de282c46f51a17 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -472,6 +472,16 @@ def __init__(self, *args, **kwargs):
git.abuse(7, 8, 9)
+ def assertDeprecated(self, name):
+ import re
+ return self.assertWarnsRegex(
+ DeprecationWarning,
+ re.escape(
+ f"{name!r} is deprecated and slated "
+ "for removal in Python 3.15",
+ ),
+ )
+
def test_abuse_done(self):
self.istest(inspect.istraceback, 'git.ex.__traceback__')
self.istest(inspect.isframe, 'mod.fr')
@@ -518,21 +528,45 @@ def test_trace(self):
self.assertEqual(frame3.positions, dis.Positions(18, 18, 8, 13))
def test_frame(self):
- args, varargs, varkw, locals = inspect.getargvalues(mod.fr)
+ with self.assertDeprecated('getargvalues'):
+ args, varargs, varkw, locals = inspect.getargvalues(mod.fr)
self.assertEqual(args, ['x', 'y'])
self.assertEqual(varargs, None)
self.assertEqual(varkw, None)
self.assertEqual(locals, {'x': 11, 'p': 11, 'y': 14})
- self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
- '(x=11, y=14)')
+ with self.assertDeprecated('formatargvalues'):
+ format = inspect.formatargvalues(args, varargs, varkw, locals)
+ self.assertEqual(format, '(x=11, y=14)')
def test_previous_frame(self):
- args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back)
+ with self.assertDeprecated('getargvalues'):
+ args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back)
self.assertEqual(args, ['a', 'b', 'c', 'd', 'e', 'f'])
self.assertEqual(varargs, 'g')
self.assertEqual(varkw, 'h')
- self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
- '(a=7, b=8, c=9, d=3, e=4, f=5, *g=(), **h={})')
+ with self.assertDeprecated('formatargvalues'):
+ format = inspect.formatargvalues(args, varargs, varkw, locals)
+ self.assertEqual(format,
+ '(a=7, b=8, c=9, d=3, e=4, f=5, *g=(), **h={})')
+
+ def test_frame_with_argument_override(self):
+ # This tests shows that the current implementation of `getargvalues`:
+ # 1. Does not render `/` correctly
+ # 2. Uses not real default values, but can also show redefined values
+ def inner(a=1, /, c=5, *, b=2):
+ global fr
+ a = 3
+ fr = inspect.currentframe()
+ b = 4
+
+ inner()
+ with self.assertDeprecated('getargvalues'):
+ args, varargs, varkw, locals = inspect.getargvalues(fr)
+ with self.assertDeprecated('formatargvalues'):
+ format = inspect.formatargvalues(args, varargs, varkw, locals)
+ self.assertEqual(format,
+ '(a=3, c=5, b=4)')
+
class GetSourceBase(unittest.TestCase):
# Subclasses must override.
@@ -4623,6 +4657,42 @@ class D2(D1):
self.assertEqual(inspect.signature(D2), inspect.signature(D1))
+class TestSignatureFromFrame(unittest.TestCase):
+ def test_signature_from_frame(self):
+ def inner(a=1, /, b=2, *e, c: int = 3, d, **f) -> None:
+ global fr
+ fr = inspect.currentframe()
+
+ inner(d=4)
+ self.assertEqual(str(inspect.Signature.from_frame(fr)),
+ '(a=1, /, b=2, *e, c=3, d=4, **f)')
+
+ def inner(a, /, b, *e, c: int = 3, d, **f) -> None:
+ global fr
+ fr = inspect.currentframe()
+
+ inner(1, 2, d=4)
+ self.assertEqual(str(inspect.Signature.from_frame(fr)),
+ '(a=1, /, b=2, *e, c=3, d=4, **f)')
+
+ def test_signature_from_frame_defaults_change(self):
+ def inner(a=1, /, c=5, *, b=2):
+ global fr
+ a = 3
+ fr = inspect.currentframe()
+ b = 4
+
+ inner()
+ self.assertEqual(str(inspect.Signature.from_frame(fr)),
+ '(a=3, /, c=5, *, b=4)')
+
+ def test_signature_from_frame_mod(self):
+ self.assertEqual(str(inspect.Signature.from_frame(mod.fr)),
+ '(x=11, y=14)')
+ self.assertEqual(str(inspect.Signature.from_frame(mod.fr.f_back)),
+ '(a=7, /, b=8, c=9, d=3, e=4, f=5, *g, **h)')
+
+
class TestParameterObject(unittest.TestCase):
def test_signature_parameter_kinds(self):
P = inspect.Parameter
diff --git a/Misc/NEWS.d/next/Library/2023-12-03-10-46-38.gh-issue-108901.lJujFe.rst b/Misc/NEWS.d/next/Library/2023-12-03-10-46-38.gh-issue-108901.lJujFe.rst
new file mode 100644
index 00000000000000..11558a72282291
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-12-03-10-46-38.gh-issue-108901.lJujFe.rst
@@ -0,0 +1,3 @@
+Deprecate :func:`inspect.getargvalues` and :func:`inspect.formatargvalues`,
+slate it for removal in 3.15; instead use
+:meth:`inspect.Signature.from_frame`.