https://github.com/python/cpython/commit/f9a7d41bacb78b9d6e095be049befdec19ffda0a
commit: f9a7d41bacb78b9d6e095be049befdec19ffda0a
branch: main
author: Ammar Askar <[email protected]>
committer: pablogsal <[email protected]>
date: 2025-02-13T01:43:09Z
summary:

gh-96092: Fix traceback.walk_stack(None) skipping too many frames (#129330)

As it says in its documentation, walk_stack was meant to just
follow `f.f_back` like other functions in the traceback module.
Instead it was previously doing `f.f_back.f_back` and then this
changed to `f_back.f_back.f_back.f_back' in Python 3.11 breaking
its behavior for external users.

This happened because the walk_stack function never really had
any good direct tests and its only consumer in the traceback module was
`extract_stack` which passed the result into `StackSummary.extract`.
As a generator, it was previously capturing the state of the stack
when it was first iterated over, rather than the stack when `walk_stack`
was called. Meaning when called inside the two method deep
`extract` and `extract_stack` calls, two `f_back`s were needed.
When 3.11 modified the sequence of calls in `extract`, two more
`f_back`s were needed to make the tests happy.

This changes the generator to capture the stack when `walk_stack` is
called, rather than when it is first iterated over. Since this is
technically a breaking change in behavior, there is a versionchanged
to the documentation. In practice, this is unlikely to break anyone,
you would have been needing to store the result of `walk_stack` and
expecting it to change.

files:
A Misc/NEWS.d/next/Library/2025-01-26-19-35-06.gh-issue-96092.mMg3gL.rst
M Doc/library/traceback.rst
M Lib/test/test_traceback.py
M Lib/traceback.py

diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst
index b0ee3fc56ad735..7e05144bfb34cb 100644
--- a/Doc/library/traceback.rst
+++ b/Doc/library/traceback.rst
@@ -257,6 +257,11 @@ Module-Level Functions
 
    .. versionadded:: 3.5
 
+   .. versionchanged:: 3.14
+      This function previously returned a generator that would walk the stack
+      when first iterated over. The generator returned now is the state of the
+      stack when ``walk_stack`` is called.
+
 .. function:: walk_tb(tb)
 
    Walk a traceback following :attr:`~traceback.tb_next` yielding the frame and
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 89980ae6f8573a..c2b115b53889d3 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -3229,11 +3229,17 @@ class TestStack(unittest.TestCase):
     def test_walk_stack(self):
         def deeper():
             return list(traceback.walk_stack(None))
-        s1 = list(traceback.walk_stack(None))
-        s2 = deeper()
+        s1, s2 = list(traceback.walk_stack(None)), deeper()
         self.assertEqual(len(s2) - len(s1), 1)
         self.assertEqual(s2[1:], s1)
 
+    def test_walk_innermost_frame(self):
+        def inner():
+            return list(traceback.walk_stack(None))
+        frames = inner()
+        innermost_frame, _ = frames[0]
+        self.assertEqual(innermost_frame.f_code.co_name, "inner")
+
     def test_walk_tb(self):
         try:
             1/0
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 31c73efcef5a52..2b402dd4cc2401 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -380,10 +380,14 @@ def walk_stack(f):
     current stack is used. Usually used with StackSummary.extract.
     """
     if f is None:
-        f = sys._getframe().f_back.f_back.f_back.f_back
-    while f is not None:
-        yield f, f.f_lineno
-        f = f.f_back
+        f = sys._getframe().f_back
+
+    def walk_stack_generator(frame):
+        while frame is not None:
+            yield frame, frame.f_lineno
+            frame = frame.f_back
+
+    return walk_stack_generator(f)
 
 
 def walk_tb(tb):
diff --git 
a/Misc/NEWS.d/next/Library/2025-01-26-19-35-06.gh-issue-96092.mMg3gL.rst 
b/Misc/NEWS.d/next/Library/2025-01-26-19-35-06.gh-issue-96092.mMg3gL.rst
new file mode 100644
index 00000000000000..623f7d278c6675
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-26-19-35-06.gh-issue-96092.mMg3gL.rst
@@ -0,0 +1,4 @@
+Fix bug in :func:`traceback.walk_stack` called with None where it was skipping
+more frames than in prior versions. This bug fix also changes walk_stack to
+walk the stack in the frame where it was called rather than where it first gets
+used.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to