diff options
author | Pablo Galindo Salgado <Pablogsal@gmail.com> | 2022-04-23 02:16:48 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-23 02:16:48 (GMT) |
commit | 0daa99f68b7b9f02b37a2f34508f33ae66d95fc4 (patch) | |
tree | 7d0ed2795e92ba42aa80f6b8e70f1bac2216c681 | |
parent | a3f2cf3ced378db2569df4e7389ec1f79c85d55c (diff) | |
download | cpython-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.rst | 126 | ||||
-rw-r--r-- | Doc/whatsnew/3.11.rst | 8 | ||||
-rw-r--r-- | Lib/inspect.py | 61 | ||||
-rw-r--r-- | Lib/test/test_inspect.py | 28 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2022-04-14-13-11-37.gh-issue-88116.j_SybE.rst | 8 |
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. + + |