gh-88116: Enhance the inspect frame APIs to use the extended position information (GH-91531)
This commit is contained in:
parent
a3f2cf3ced
commit
0daa99f68b
@ -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
|
||||
------------------------------
|
||||
|
@ -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
|
||||
------
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user