我的机器学习教程「美团」算法工程师带你入门机器学习 已经开始更新了,欢迎大家订阅~
任何关于算法、编程、AI行业知识或博客内容的问题,可以随时扫码关注公众号「图灵的猫」,加入”学习小组“,沙雕博主在线答疑~此外,公众号内还有更多AI、算法、编程和大数据知识分享,以及免费的SSR节点和学习资料。其他平台(知乎/B站)也是同名「图灵的猫」,不要迷路哦~
一般递归
1
2
3
4
5
6
|
def
normal_recursion
(
n
)
:
if
n
==
1
:
return
1
else
:
return
n
+
normal_recursion
(
n
-
1
)
|
执行:
1
2
3
4
5
6
7
8
9
10
|
normal_recursion
(
5
)
5
+
normal_recursion
(
4
)
5
+
4
+
normal_recursion
(
3
)
5
+
4
+
3
+
normal_recursion
(
2
)
5
+
4
+
3
+
2
+
normal_recursion
(
1
)
5
+
4
+
3
+
3
5
+
4
+
6
5
+
10
15
|
可以看到, 一般递归, 每一级递归都需要调用函数, 会创建新的栈,
随着递归深度的增加, 创建的栈越来越多, 造成爆栈
尾递归
尾递归基于函数的尾调用, 每一级调用直接返回函数的返回值更新调用栈,而不用创建新的调用栈, 类似迭代的实现, 时间和空间上均优化了一般递归!
1
2
3
4
5
6
|
def
tail_recursion
(
n
,
total
=
0
)
:
if
n
==
0
:
return
total
else
:
return
tail_recursion
(
n
-
1
,
total
+
n
)
|
执行:
1
2
3
4
5
6
7
8
|
tail_recursion
(
5
)
tail_recursion
(
4
,
5
)
tail_recursion
(
3
,
9
)
tail_recursion
(
2
,
12
)
tail_recursion
(
1
,
14
)
tail_recursion
(
0
,
15
)
15
|
可以看到, 每一级递归的函数调用变成”线性”的形式.
呃, 所以呢? 是不是感觉还不够过瘾… 谁说尾递归调用就不用创建新的栈呢?
还是让我们去底层一探究竟吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
int
tail_recursion
(
int
n
,
int
total
)
{
if
(
n
==
0
)
{
return
total
;
}
else
{
return
tail_recursion
(
n
-
1
,
total
+
n
)
;
}
}
int
main
(
void
)
{
int
total
=
0
,
n
=
4
;
tail_recursion
(
n
,
total
)
;
return
0
;
}
|
反汇编
$ gcc -S tail_recursion.c -o normal_recursion.S
$ gcc -S -O2 tail_recursion.c -o tail_recursion.S
gcc开启尾递归优化对比反汇编代码如下(AT&T语法)
可以看到, 开启尾递归优化前, 使用call调用函数, 创建了新的调用栈(LBB0_3);
而开启尾递归优化后, 就没有新的调用栈生成了, 而是直接pop
bp指向的_tail_recursion
函数的地址(pushq %rbp)然后返回,
仍旧用的是同一个调用栈!
虽然尾递归优化很好, 但python 不支持尾递归,递归深度超过1000时会报错
1
2
|
RuntimeError
:
maximum
recursion
depth exceeded
|
实现一个 tail_call_optimized 装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#!/usr/bin/env python2.4
# This program shows off a python decorator(
# which implements tail call optimization. It
# does this by throwing an exception if it is
# it's own grandparent, and catching such
# exceptions to recall the stack.
import
sys
class
TailRecurseException
:
def
__init__
(
self
,
args
,
kwargs
)
:
self
.
args
=
args
self
.
kwargs
=
kwargs
def
tail_call_optimized
(
g
)
:
""
"
This function decorates a function with tail call
optimization. It does this by throwing an exception
if it is it's own grandparent, and catching such
exceptions to fake the tail call optimization.
This function fails if the decorated
function recurses in a non-tail context.
"
""
def
func
(
*
args
,
*
*
kwargs
)
:
f
=
sys
.
_getframe
(
)
# 为什么是grandparent, 函数默认的第一层递归是父调用,
# 对于尾递归, 不希望产生新的函数调用(即:祖父调用),
# 所以这里抛出异常, 拿到参数, 退出被修饰函数的递归调用栈!(后面有动图分析)
if
f
.
f_back
and
f
.
f_back
.
f
_back
\
and
f
.
f_back
.
f_back
.
f_code
==
f
.
f_code
:
# 抛出异常
raise
TailRecurseException
(
args
,
kwargs
)
else
:
while
1
:
try
:
return
g
(
*
args
,
*
*
kwargs
)
except
TailRecurseException
,
e
:
# 捕获异常, 拿到参数, 退出被修饰函数的递归调用栈
args
=
e
.
args
kwargs
=
e
.
kwargs
func
.
__doc__
=
g
.
__doc__
return
func
@
tail_call_optimized
def
factorial
(
n
,
acc
=
1
)
:
"calculate a factorial"
if
n
==
0
:
return
acc
return
factorial
(
n
-
1
,
n*
acc
)
print
factorial
(
10000
)
|
为了更清晰的展示开启尾递归优化前、后调用栈的变化和tail_call_optimized装饰器抛异常退出递归调用栈的作用, 我这里利用pudb调试工具做了动图
开启尾递归优化前的调用栈
开启尾递归优化后(tail_call_optimized装饰器)的调用栈
通过pudb右边栏的stack, 可以很清晰的看到调用栈的变化.
因为尾递归没有调用栈的嵌套, 所以Python也不会报RuntimeError: maximum recursion depth exceeded
错误了!
这里解释一下 sys._getframe() 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
sys
.
_getframe
(
[
depth
]
)
:
Return
a
frame
object
from
the
call
stack
.
If
optional
integer
depth
is
given
,
return
the
frame
object
that
many
calls
below
the
top
of
the
stack
.
If
that
is
deeper
than
the
call
stack
,
ValueEfror
is
raised
.
The
default
for
depth
is
zero
,
returning
the
frame
at
the
top
of
the
call
stack
.
即返回depth深度调用的栈帧对象
.
import
sys
def
get_cur_info
(
)
:
print
sys
.
_getframe
(
)
.
f_code
.
co_filename
# 当前文件名
print
sys
.
_getframe
(
)
.
f_code
.
co_name
# 当前函数名
print
sys
.
_getframe
(
)
.
f_lineno
# 当前行号
print
sys
.
_getframe
(
)
.
f_back
# 调用者的帧
|
更多关于sys._getframe
请看Frame Hacks