递归函数的栈帧工作原理解析及Python递归列表打印的栈帧差异疑问解答
为什么调整递归中
print的顺序会导致输出正/倒序? 这问题的核心其实就是递归调用的栈帧执行顺序——你可以把每个递归调用想象成一个“暂停的任务”,Python会把这些任务按调用顺序堆叠起来(这就是栈的“后进先出”特性),只有当最顶层的任务完成后,才会回头处理下面的任务。
先看正序输出的情况
原函数代码:
def printout(list): if len(list) > 0: print(list[0]) printout(list[1:])
当传入list1 = [5,6,7,8,9]时,执行流程是这样的:
- 第一个
printout([5,6,7,8,9])先执行print(5),然后暂停自己,去调用printout([6,7,8,9]) - 第二个
printout([6,7,8,9])先执行print(6),暂停自己,调用printout([7,8,9]) - 以此类推,直到
printout([9])执行print(9),然后调用printout([]) - 当遇到空列表时,函数直接返回(没有可执行的代码),这时候栈里的“暂停任务”开始依次收尾:从
printout([9])回到printout([8,9]),但后者已经做完了所有该做的,直接返回,直到最开始的printout([5,6,7,8,9])收尾完成。
整个过程中,每个任务在暂停前都已经执行了print,所以输出就是正序的5 6 7 8 9。
对应的栈帧变化(从栈底到栈顶):
- 栈帧1:参数
list=[5,6,7,8,9],执行到printout(list[1:])行(已完成print) - 栈帧2:参数
list=[6,7,8,9],执行到printout(list[1:])行(已完成print) - 栈帧3:参数
list=[7,8,9],执行到printout(list[1:])行(已完成print) - 栈帧4:参数
list=[8,9],执行到printout(list[1:])行(已完成print) - 栈帧5:参数
list=[9],执行到printout(list[1:])行(已完成print) - 栈帧6:参数
list=[],条件不满足,直接返回
当栈帧6返回后,栈帧5到栈帧1依次弹出,每个都没有剩余代码需要执行,直接结束。
再看倒序输出的情况
修改后的函数代码:
def printout(list): if len(list) > 0: printout(list[1:]) print(list[0])
这时候执行顺序完全反过来了:
- 第一个
printout([5,6,7,8,9])先暂停自己,直接调用printout([6,7,8,9]),还没执行print(5) - 第二个
printout([6,7,8,9])同样暂停自己,调用printout([7,8,9]),也没执行print(6) - 一直到
printout([9])暂停自己,调用printout([]) - 当
printout([])返回后,栈顶的printout([9])才会执行剩下的print(9),然后返回 - 接着栈顶变成
printout([8,9]),执行print(8),返回 - 以此类推,直到最开始的
printout([5,6,7,8,9])执行print(5),整个过程结束。
因为每个任务都是在子任务完成后才执行print,所以输出就是倒序的9 8 7 6 5。
对应的栈帧变化(从栈底到栈顶):
- 栈帧1:参数
list=[5,6,7,8,9],执行到print(list[0])行(还没执行) - 栈帧2:参数
list=[6,7,8,9],执行到print(list[0])行(还没执行) - 栈帧3:参数
list=[7,8,9],执行到print(list[0])行(还没执行) - 栈帧4:参数
list=[8,9],执行到print(list[0])行(还没执行) - 栈帧5:参数
list=[9],执行到print(list[0])行(还没执行) - 栈帧6:参数
list=[],条件不满足,直接返回
当栈帧6返回后,栈帧5先执行print(9)再弹出,接着栈帧4执行print(8)再弹出……直到栈帧1执行print(5)后弹出,所有任务完成。
简单总结:递归调用就像叠盘子,先放的盘子在最下面,后放的在最上面。正序是放盘子前先做标记,倒序是等所有盘子叠完,从最上面开始做标记。
内容的提问来源于stack exchange,提问作者PScott




