递归¶
函数调用另外一个函数是合法的,那么函数调用自己也是合法的。调用自己的函数称为递归的(recursive)函数,这个执行的过程叫作递归(recursion)。这样做有什么好处可能还不明显,但它其实是程序能做的最神奇的事情之一。
定义递归函数很容易,例如:
def factorial(n):
if n <= 1: return 1
else: return n * factorial(n - 1)
但是要注意,Python 对于递归函数调用的深度做了限制。函数 sys.getrecursionlimit() 返回当前最大的递归深度,而函数 sys.setrecursionlimit() 可用于修改这个值。默认值为 1000。尽管可以增大这个值,但程序仍然会受主机操作系统使用的栈大小限制。超出递归深度时,就会引发 RuntimeError 异常。和其他函数式编程语言(如 Scheme)不同,Python 不会进行尾递归优化。
递归不能用在生成器函数和协程中。例如,下面这段代码打印了一个嵌套列表集中的所有项:
def flatten(lists):
for s in lists:
if isinstance(s,list):
flatten(s)
else:
print(s)
items = [[1,2,3],[4,5,[5,6]],[7,8,9]]
flatten(items) # 打印结果为 1 2 3 4 5 6 7 8 9
但是,如果将 print 操作改为 yield 语句,这段代码就无法工作。这是因为对 flatten() 函数的递归调用只会创建一个新的生成器对象,而不会实际迭代它。下面给出了递归生成器的有效版本:
def genflatten(lists):
for s in lists:
if isinstance(s,list):
for item in genflatten(s):
yield item
else:
yield item
还要当心混合使用递归函数和装饰器的问题。如果对递归函数使用装饰器,所有内部的递归调用都会通过装饰后的版本进行,例如:
@locked
def factorial(n):
if n <= 1: return 1
else: return n * factorial(n - 1) # 调用 factorial 函数的已包装版本
如果使用装饰器的目的是进行一些系统管理,如同步或锁定,最好不要使用递归。