mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-88116: Enhance the inspect frame APIs to use the extended position information (GH-91531)
This commit is contained in:
parent
a3f2cf3ced
commit
0daa99f68b
5 changed files with 193 additions and 38 deletions
|
@ -1163,17 +1163,85 @@ Classes and functions
|
||||||
The interpreter stack
|
The interpreter stack
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
When the following functions return "frame records," each record is a
|
Some of the following functions return
|
||||||
:term:`named tuple`
|
:class:`FrameInfo` objects. For backwards compatibility these objects allow
|
||||||
``FrameInfo(frame, filename, lineno, function, code_context, index)``.
|
tuple-like operations on all attributes except ``positions``. This behavior
|
||||||
The tuple contains the frame object, the filename, the line number of the
|
is considered deprecated and may be removed in the future.
|
||||||
current line,
|
|
||||||
the function name, a list of lines of context from the source code, and the
|
.. class:: FrameInfo
|
||||||
index of the current line within that list.
|
|
||||||
|
.. 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
|
.. versionchanged:: 3.5
|
||||||
Return a named tuple instead of a tuple.
|
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::
|
.. note::
|
||||||
|
|
||||||
Keeping references to frame objects, as found in the first element of the frame
|
Keeping references to frame objects, as found in the first element of the frame
|
||||||
|
@ -1207,35 +1275,41 @@ line.
|
||||||
|
|
||||||
.. function:: getframeinfo(frame, context=1)
|
.. function:: getframeinfo(frame, context=1)
|
||||||
|
|
||||||
Get information about a frame or traceback object. A :term:`named tuple`
|
Get information about a frame or traceback object. A :class:`Traceback` object
|
||||||
``Traceback(filename, lineno, function, code_context, index)`` is returned.
|
is returned.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
A :class:`Traceback` object is returned instead of a named tuple.
|
||||||
|
|
||||||
.. function:: getouterframes(frame, context=1)
|
.. function:: getouterframes(frame, context=1)
|
||||||
|
|
||||||
Get a list of frame records for a frame and all outer frames. These frames
|
Get a list of :class:`FrameInfo` objects for a frame and all outer frames.
|
||||||
represent the calls that lead to the creation of *frame*. The first entry in the
|
These frames represent the calls that lead to the creation of *frame*. The
|
||||||
returned list represents *frame*; the last entry represents the outermost call
|
first entry in the returned list represents *frame*; the last entry
|
||||||
on *frame*'s stack.
|
represents the outermost call on *frame*'s stack.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
A list of :term:`named tuples <named tuple>`
|
A list of :term:`named tuples <named tuple>`
|
||||||
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
A list of :class:`FrameInfo` objects is returned.
|
||||||
|
|
||||||
.. function:: getinnerframes(traceback, context=1)
|
.. function:: getinnerframes(traceback, context=1)
|
||||||
|
|
||||||
Get a list of frame records for a traceback's frame and all inner frames. These
|
Get a list of :class:`FrameInfo` objects for a traceback's frame and all
|
||||||
frames represent calls made as a consequence of *frame*. The first entry in the
|
inner frames. These frames represent calls made as a consequence of *frame*.
|
||||||
list represents *traceback*; the last entry represents where the exception was
|
The first entry in the list represents *traceback*; the last entry represents
|
||||||
raised.
|
where the exception was raised.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
A list of :term:`named tuples <named tuple>`
|
A list of :term:`named tuples <named tuple>`
|
||||||
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
A list of :class:`FrameInfo` objects is returned.
|
||||||
|
|
||||||
.. function:: currentframe()
|
.. function:: currentframe()
|
||||||
|
|
||||||
|
@ -1251,28 +1325,32 @@ line.
|
||||||
|
|
||||||
.. function:: stack(context=1)
|
.. function:: stack(context=1)
|
||||||
|
|
||||||
Return a list of frame records for the caller's stack. The first entry in the
|
Return a list of :class:`FrameInfo` objects for the caller's stack. The
|
||||||
returned list represents the caller; the last entry represents the outermost
|
first entry in the returned list represents the caller; the last entry
|
||||||
call on the stack.
|
represents the outermost call on the stack.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
A list of :term:`named tuples <named tuple>`
|
A list of :term:`named tuples <named tuple>`
|
||||||
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
A list of :class:`FrameInfo` objects is returned.
|
||||||
|
|
||||||
.. function:: trace(context=1)
|
.. function:: trace(context=1)
|
||||||
|
|
||||||
Return a list of frame records for the stack between the current frame and the
|
Return a list of :class:`FrameInfo` objects for the stack between the current
|
||||||
frame in which an exception currently being handled was raised in. The first
|
frame and the frame in which an exception currently being handled was raised
|
||||||
entry in the list represents the caller; the last entry represents where the
|
in. The first entry in the list represents the caller; the last entry
|
||||||
exception was raised.
|
represents where the exception was raised.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
A list of :term:`named tuples <named tuple>`
|
A list of :term:`named tuples <named tuple>`
|
||||||
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
``FrameInfo(frame, filename, lineno, function, code_context, index)``
|
||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
A list of :class:`FrameInfo` objects is returned.
|
||||||
|
|
||||||
Fetching attributes statically
|
Fetching attributes statically
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
|
@ -326,6 +326,14 @@ inspect
|
||||||
* Add :func:`inspect.ismethodwrapper` for checking if the type of an object is a
|
* Add :func:`inspect.ismethodwrapper` for checking if the type of an object is a
|
||||||
:class:`~types.MethodWrapperType`. (Contributed by Hakan Çelik in :issue:`29418`.)
|
: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
|
locale
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -1638,7 +1638,30 @@ def getclosurevars(func):
|
||||||
|
|
||||||
# -------------------------------------------------- stack frame extraction
|
# -------------------------------------------------- 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):
|
def getframeinfo(frame, context=1):
|
||||||
"""Get information about a frame or traceback object.
|
"""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
|
The optional second argument specifies the number of lines of context
|
||||||
to return, which are centered around the current line."""
|
to return, which are centered around the current line."""
|
||||||
if istraceback(frame):
|
if istraceback(frame):
|
||||||
|
positions = _get_code_position_from_tb(frame)
|
||||||
lineno = frame.tb_lineno
|
lineno = frame.tb_lineno
|
||||||
frame = frame.tb_frame
|
frame = frame.tb_frame
|
||||||
else:
|
else:
|
||||||
lineno = frame.f_lineno
|
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):
|
if not isframe(frame):
|
||||||
raise TypeError('{!r} is not a frame or traceback object'.format(frame))
|
raise TypeError('{!r} is not a frame or traceback object'.format(frame))
|
||||||
|
|
||||||
|
@ -1670,14 +1703,26 @@ def getframeinfo(frame, context=1):
|
||||||
else:
|
else:
|
||||||
lines = index = None
|
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):
|
def getlineno(frame):
|
||||||
"""Get the line number from a frame object, allowing for optimization."""
|
"""Get the line number from a frame object, allowing for optimization."""
|
||||||
# FrameType.f_lineno is now a descriptor that grovels co_lnotab
|
# FrameType.f_lineno is now a descriptor that grovels co_lnotab
|
||||||
return frame.f_lineno
|
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):
|
def getouterframes(frame, context=1):
|
||||||
"""Get a list of records for a frame and all higher (calling) frames.
|
"""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."""
|
name, a list of lines of context, and index within the context."""
|
||||||
framelist = []
|
framelist = []
|
||||||
while frame:
|
while frame:
|
||||||
frameinfo = (frame,) + getframeinfo(frame, context)
|
traceback_info = getframeinfo(frame, context)
|
||||||
framelist.append(FrameInfo(*frameinfo))
|
frameinfo = (frame,) + traceback_info
|
||||||
|
framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions))
|
||||||
frame = frame.f_back
|
frame = frame.f_back
|
||||||
return framelist
|
return framelist
|
||||||
|
|
||||||
|
@ -1698,8 +1744,9 @@ def getinnerframes(tb, context=1):
|
||||||
name, a list of lines of context, and index within the context."""
|
name, a list of lines of context, and index within the context."""
|
||||||
framelist = []
|
framelist = []
|
||||||
while tb:
|
while tb:
|
||||||
frameinfo = (tb.tb_frame,) + getframeinfo(tb, context)
|
traceback_info = getframeinfo(tb, context)
|
||||||
framelist.append(FrameInfo(*frameinfo))
|
frameinfo = (tb.tb_frame,) + traceback_info
|
||||||
|
framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions))
|
||||||
tb = tb.tb_next
|
tb = tb.tb_next
|
||||||
return framelist
|
return framelist
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import inspect
|
||||||
import io
|
import io
|
||||||
import linecache
|
import linecache
|
||||||
import os
|
import os
|
||||||
|
import dis
|
||||||
from os.path import normcase
|
from os.path import normcase
|
||||||
import _pickle
|
import _pickle
|
||||||
import pickle
|
import pickle
|
||||||
|
@ -361,14 +362,23 @@ class TestInterpreterStack(IsTestBase):
|
||||||
|
|
||||||
def test_stack(self):
|
def test_stack(self):
|
||||||
self.assertTrue(len(mod.st) >= 5)
|
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))
|
(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))
|
(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))
|
(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))
|
(modfile, 39, 'abuse', [' self.argue(a, b, c)\n'], 0))
|
||||||
|
self.assertEqual(frame4.positions, dis.Positions(39, 39, 8, 27))
|
||||||
# Test named tuple fields
|
# Test named tuple fields
|
||||||
record = mod.st[0]
|
record = mod.st[0]
|
||||||
self.assertIs(record.frame, mod.fr)
|
self.assertIs(record.frame, mod.fr)
|
||||||
|
@ -380,12 +390,16 @@ class TestInterpreterStack(IsTestBase):
|
||||||
|
|
||||||
def test_trace(self):
|
def test_trace(self):
|
||||||
self.assertEqual(len(git.tr), 3)
|
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))
|
(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))
|
(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))
|
(modfile, 18, 'eggs', [' q = y / 0\n'], 0))
|
||||||
|
self.assertEqual(frame3.positions, dis.Positions(18, 18, 8, 13))
|
||||||
|
|
||||||
def test_frame(self):
|
def test_frame(self):
|
||||||
args, varargs, varkw, locals = inspect.getargvalues(mod.fr)
|
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…
Add table
Add a link
Reference in a new issue