summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPablo Galindo Salgado <Pablogsal@gmail.com>2022-04-23 02:16:48 (GMT)
committerGitHub <noreply@github.com>2022-04-23 02:16:48 (GMT)
commit0daa99f68b7b9f02b37a2f34508f33ae66d95fc4 (patch)
tree7d0ed2795e92ba42aa80f6b8e70f1bac2216c681
parenta3f2cf3ced378db2569df4e7389ec1f79c85d55c (diff)
downloadcpython-0daa99f68b7b9f02b37a2f34508f33ae66d95fc4.zip
cpython-0daa99f68b7b9f02b37a2f34508f33ae66d95fc4.tar.gz
cpython-0daa99f68b7b9f02b37a2f34508f33ae66d95fc4.tar.bz2
gh-88116: Enhance the inspect frame APIs to use the extended position information (GH-91531)
-rw-r--r--Doc/library/inspect.rst126
-rw-r--r--Doc/whatsnew/3.11.rst8
-rw-r--r--Lib/inspect.py61
-rw-r--r--Lib/test/test_inspect.py28
-rw-r--r--Misc/NEWS.d/next/Library/2022-04-14-13-11-37.gh-issue-88116.j_SybE.rst8
5 files changed, 193 insertions, 38 deletions
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 8ee2c07..575b308 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -1163,17 +1163,85 @@ Classes and functions
The interpreter stack
---------------------
-When the following functions return "frame records," each record is a
-:term:`named tuple`
-``FrameInfo(frame, filename, lineno, function, code_context, index)``.
-The tuple contains the frame object, the filename, the line number of the
-current line,
-the function name, a list of lines of context from the source code, and the
-index of the current line within that list.
+Some of the following functions return
+:class:`FrameInfo` objects. For backwards compatibility these objects allow
+tuple-like operations on all attributes except ``positions``. This behavior
+is considered deprecated and may be removed in the future.
+
+.. class:: FrameInfo
+
+ .. attribute:: frame
+
+ The :ref:`frame object <frame-objects>` that the record corresponds to.
+
+ .. attribute:: filename
+
+ The file name associated with the code being executed by the frame this record
+ corresponds to.
+
+ .. attribute:: lineno
+
+ The line number of the current line associated with the code being
+ executed by the frame this record corresponds to.
+
+ .. attribute:: function
+
+ The function name that is being executed by the frame this record corresponds to.
+
+ .. attribute:: code_context
+
+ A list of lines of context from the source code that's being executed by the frame
+ this record corresponds to.
+
+ .. attribute:: index
+
+ The index of the current line being executed in the :attr:`code_context` list.
+
+ .. attribute:: positions
+
+ A :class:`dis.Positions` object containing the start line number, end line
+ number, start column offset, and end column offset associated with the
+ instruction being executed by the frame this record corresponds to.
.. versionchanged:: 3.5
Return a named tuple instead of a tuple.
+.. versionchanged:: 3.11
+ Changed the return object from a named tuple to a regular object (that is
+ backwards compatible with the previous named tuple).
+
+.. class:: Traceback
+
+ .. attribute:: filename
+
+ The file name associated with the code being executed by the frame this traceback
+ corresponds to.
+
+ .. attribute:: lineno
+
+ The line number of the current line associated with the code being
+ executed by the frame this traceback corresponds to.
+
+ .. attribute:: function
+
+ The function name that is being executed by the frame this traceback corresponds to.
+
+ .. attribute:: code_context
+
+ A list of lines of context from the source code that's being executed by the frame
+ this traceback corresponds to.
+
+ .. attribute:: index
+
+ The index of the current line being executed in the :attr:`code_context` list.
+
+ .. attribute:: positions
+
+ A :class:`dis.Positions` object containing the start line number, end
+ line number, start column offset, and end column offset associated with
+ the instruction being executed by the frame this traceback corresponds
+ to.
+
.. note::
Keeping references to frame objects, as found in the first element of the frame
@@ -1207,35 +1275,41 @@ line.
.. function:: getframeinfo(frame, context=1)
- Get information about a frame or traceback object. A :term:`named tuple`
- ``Traceback(filename, lineno, function, code_context, index)`` is returned.
+ Get information about a frame or traceback object. A :class:`Traceback` object
+ is returned.
+ .. versionchanged:: 3.11
+ A :class:`Traceback` object is returned instead of a named tuple.
.. function:: getouterframes(frame, context=1)
- Get a list of frame records for a frame and all outer frames. These frames
- represent the calls that lead to the creation of *frame*. The first entry in the
- returned list represents *frame*; the last entry represents the outermost call
- on *frame*'s stack.
+ Get a list of :class:`FrameInfo` objects for a frame and all outer frames.
+ These frames represent the calls that lead to the creation of *frame*. The
+ first entry in the returned list represents *frame*; the last entry
+ represents the outermost call on *frame*'s stack.
.. versionchanged:: 3.5
A list of :term:`named tuples <named tuple>`
``FrameInfo(frame, filename, lineno, function, code_context, index)``
is returned.
+ .. versionchanged:: 3.11
+ A list of :class:`FrameInfo` objects is returned.
.. function:: getinnerframes(traceback, context=1)
- Get a list of frame records for a traceback's frame and all inner frames. These
- frames represent calls made as a consequence of *frame*. The first entry in the
- list represents *traceback*; the last entry represents where the exception was
- raised.
+ Get a list of :class:`FrameInfo` objects for a traceback's frame and all
+ inner frames. These frames represent calls made as a consequence of *frame*.
+ The first entry in the list represents *traceback*; the last entry represents
+ where the exception was raised.
.. versionchanged:: 3.5
A list of :term:`named tuples <named tuple>`
``FrameInfo(frame, filename, lineno, function, code_context, index)``
is returned.
+ .. versionchanged:: 3.11
+ A list of :class:`FrameInfo` objects is returned.
.. function:: currentframe()
@@ -1251,28 +1325,32 @@ line.
.. function:: stack(context=1)
- Return a list of frame records for the caller's stack. The first entry in the
- returned list represents the caller; the last entry represents the outermost
- call on the stack.
+ Return a list of :class:`FrameInfo` objects for the caller's stack. The
+ first entry in the returned list represents the caller; the last entry
+ represents the outermost call on the stack.
.. versionchanged:: 3.5
A list of :term:`named tuples <named tuple>`
``FrameInfo(frame, filename, lineno, function, code_context, index)``
is returned.
+ .. versionchanged:: 3.11
+ A list of :class:`FrameInfo` objects is returned.
.. function:: trace(context=1)
- Return a list of frame records for the stack between the current frame and the
- frame in which an exception currently being handled was raised in. The first
- entry in the list represents the caller; the last entry represents where the
- exception was raised.
+ Return a list of :class:`FrameInfo` objects for the stack between the current
+ frame and the frame in which an exception currently being handled was raised
+ in. The first entry in the list represents the caller; the last entry
+ represents where the exception was raised.
.. versionchanged:: 3.5
A list of :term:`named tuples <named tuple>`
``FrameInfo(frame, filename, lineno, function, code_context, index)``
is returned.
+ .. versionchanged:: 3.11
+ A list of :class:`FrameInfo` objects is returned.
Fetching attributes statically
------------------------------
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 653d32a..5b53fbe 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -326,6 +326,14 @@ inspect
* Add :func:`inspect.ismethodwrapper` for checking if the type of an object is a
:class:`~types.MethodWrapperType`. (Contributed by Hakan Çelik in :issue:`29418`.)
+* Change the frame-related functions in the :mod:`inspect` module to return a
+ regular object (that is backwards compatible with the old tuple-like
+ interface) that include the extended :pep:`657` position information (end
+ line number, column and end column). The affected functions are:
+ :func:`inspect.getframeinfo`, :func:`inspect.getouterframes`, :func:`inspect.getinnerframes`,
+ :func:`inspect.stack` and :func:`inspect.trace`. (Contributed by Pablo Galindo in
+ :issue:`88116`)
+
locale
------
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 9c1283a..5bc9c04 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1638,7 +1638,30 @@ def getclosurevars(func):
# -------------------------------------------------- stack frame extraction
-Traceback = namedtuple('Traceback', 'filename lineno function code_context index')
+_Traceback = namedtuple('_Traceback', 'filename lineno function code_context index')
+
+class Traceback(_Traceback):
+ def __new__(cls, filename, lineno, function, code_context, index, *, positions=None):
+ instance = super().__new__(cls, filename, lineno, function, code_context, index)
+ instance.positions = positions
+ return instance
+
+ def __repr__(self):
+ return ('Traceback(filename={!r}, lineno={!r}, function={!r}, '
+ 'code_context={!r}, index={!r}, positions={!r})'.format(
+ self.filename, self.lineno, self.function, self.code_context,
+ self.index, self.positions))
+
+def _get_code_position_from_tb(tb):
+ code, instruction_index = tb.tb_frame.f_code, tb.tb_lasti
+ return _get_code_position(code, instruction_index)
+
+def _get_code_position(code, instruction_index):
+ if instruction_index < 0:
+ return (None, None, None, None)
+ positions_gen = code.co_positions()
+ # The nth entry in code.co_positions() corresponds to instruction (2*n)th since Python 3.10+
+ return next(itertools.islice(positions_gen, instruction_index // 2, None))
def getframeinfo(frame, context=1):
"""Get information about a frame or traceback object.
@@ -1649,10 +1672,20 @@ def getframeinfo(frame, context=1):
The optional second argument specifies the number of lines of context
to return, which are centered around the current line."""
if istraceback(frame):
+ positions = _get_code_position_from_tb(frame)
lineno = frame.tb_lineno
frame = frame.tb_frame
else:
lineno = frame.f_lineno
+ positions = _get_code_position(frame.f_code, frame.f_lasti)
+
+ if positions[0] is None:
+ frame, *positions = (frame, lineno, *positions[1:])
+ else:
+ frame, *positions = (frame, *positions)
+
+ lineno = positions[0]
+
if not isframe(frame):
raise TypeError('{!r} is not a frame or traceback object'.format(frame))
@@ -1670,14 +1703,26 @@ def getframeinfo(frame, context=1):
else:
lines = index = None
- return Traceback(filename, lineno, frame.f_code.co_name, lines, index)
+ return Traceback(filename, lineno, frame.f_code.co_name, lines,
+ index, positions=dis.Positions(*positions))
def getlineno(frame):
"""Get the line number from a frame object, allowing for optimization."""
# FrameType.f_lineno is now a descriptor that grovels co_lnotab
return frame.f_lineno
-FrameInfo = namedtuple('FrameInfo', ('frame',) + Traceback._fields)
+_FrameInfo = namedtuple('_FrameInfo', ('frame',) + Traceback._fields)
+class FrameInfo(_FrameInfo):
+ def __new__(cls, frame, filename, lineno, function, code_context, index, *, positions=None):
+ instance = super().__new__(cls, frame, filename, lineno, function, code_context, index)
+ instance.positions = positions
+ return instance
+
+ def __repr__(self):
+ return ('FrameInfo(frame={!r}, filename={!r}, lineno={!r}, function={!r}, '
+ 'code_context={!r}, index={!r}, positions={!r})'.format(
+ self.frame, self.filename, self.lineno, self.function,
+ self.code_context, self.index, self.positions))
def getouterframes(frame, context=1):
"""Get a list of records for a frame and all higher (calling) frames.
@@ -1686,8 +1731,9 @@ def getouterframes(frame, context=1):
name, a list of lines of context, and index within the context."""
framelist = []
while frame:
- frameinfo = (frame,) + getframeinfo(frame, context)
- framelist.append(FrameInfo(*frameinfo))
+ traceback_info = getframeinfo(frame, context)
+ frameinfo = (frame,) + traceback_info
+ framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions))
frame = frame.f_back
return framelist
@@ -1698,8 +1744,9 @@ def getinnerframes(tb, context=1):
name, a list of lines of context, and index within the context."""
framelist = []
while tb:
- frameinfo = (tb.tb_frame,) + getframeinfo(tb, context)
- framelist.append(FrameInfo(*frameinfo))
+ traceback_info = getframeinfo(tb, context)
+ frameinfo = (tb.tb_frame,) + traceback_info
+ framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions))
tb = tb.tb_next
return framelist
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 9e3c770..115e97b 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -7,6 +7,7 @@ import inspect
import io
import linecache
import os
+import dis
from os.path import normcase
import _pickle
import pickle
@@ -361,14 +362,23 @@ class TestInterpreterStack(IsTestBase):
def test_stack(self):
self.assertTrue(len(mod.st) >= 5)
- self.assertEqual(revise(*mod.st[0][1:]),
+ frame1, frame2, frame3, frame4, *_ = mod.st
+ frameinfo = revise(*frame1[1:])
+ self.assertEqual(frameinfo,
(modfile, 16, 'eggs', [' st = inspect.stack()\n'], 0))
- self.assertEqual(revise(*mod.st[1][1:]),
+ self.assertEqual(frame1.positions, dis.Positions(16, 16, 9, 24))
+ frameinfo = revise(*frame2[1:])
+ self.assertEqual(frameinfo,
(modfile, 9, 'spam', [' eggs(b + d, c + f)\n'], 0))
- self.assertEqual(revise(*mod.st[2][1:]),
+ self.assertEqual(frame2.positions, dis.Positions(9, 9, 4, 22))
+ frameinfo = revise(*frame3[1:])
+ self.assertEqual(frameinfo,
(modfile, 43, 'argue', [' spam(a, b, c)\n'], 0))
- self.assertEqual(revise(*mod.st[3][1:]),
+ self.assertEqual(frame3.positions, dis.Positions(43, 43, 12, 25))
+ frameinfo = revise(*frame4[1:])
+ self.assertEqual(frameinfo,
(modfile, 39, 'abuse', [' self.argue(a, b, c)\n'], 0))
+ self.assertEqual(frame4.positions, dis.Positions(39, 39, 8, 27))
# Test named tuple fields
record = mod.st[0]
self.assertIs(record.frame, mod.fr)
@@ -380,12 +390,16 @@ class TestInterpreterStack(IsTestBase):
def test_trace(self):
self.assertEqual(len(git.tr), 3)
- self.assertEqual(revise(*git.tr[0][1:]),
+ frame1, frame2, frame3, = git.tr
+ self.assertEqual(revise(*frame1[1:]),
(modfile, 43, 'argue', [' spam(a, b, c)\n'], 0))
- self.assertEqual(revise(*git.tr[1][1:]),
+ self.assertEqual(frame1.positions, dis.Positions(43, 43, 12, 25))
+ self.assertEqual(revise(*frame2[1:]),
(modfile, 9, 'spam', [' eggs(b + d, c + f)\n'], 0))
- self.assertEqual(revise(*git.tr[2][1:]),
+ self.assertEqual(frame2.positions, dis.Positions(9, 9, 4, 22))
+ self.assertEqual(revise(*frame3[1:]),
(modfile, 18, 'eggs', [' q = y / 0\n'], 0))
+ self.assertEqual(frame3.positions, dis.Positions(18, 18, 8, 13))
def test_frame(self):
args, varargs, varkw, locals = inspect.getargvalues(mod.fr)
diff --git a/Misc/NEWS.d/next/Library/2022-04-14-13-11-37.gh-issue-88116.j_SybE.rst b/Misc/NEWS.d/next/Library/2022-04-14-13-11-37.gh-issue-88116.j_SybE.rst
new file mode 100644
index 0000000..1b7bd69
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-04-14-13-11-37.gh-issue-88116.j_SybE.rst
@@ -0,0 +1,8 @@
+Change the frame-related functions in the :mod:`inspect` module to return a
+regular object (that is backwards compatible with the old tuple-like interface)
+that include the extended :pep:`657` position information (end line number,
+column and end column). The affected functions are: :func:`inspect.getframeinfo`,
+:func:`inspect.getouterframes`, :func:`inspect.getinnerframes`, :func:`inspect.stack` and
+:func:`inspect.trace`. Patch by Pablo Galindo.
+
+