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.
.. 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
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
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):
--- /dev/null
+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.