在思否上面看到了這樣一篇的文章:講述了如何去除python對遞歸的限制,看完后不得不對他佩服,不過仔細想想也是挺合理的!他主要是通過拋出異常來結束之前的棧然后新開棧來調用函數,具體代碼如下:
#!/usr/bin/env python3.5
# 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(Exception):
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()
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 as 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(2000))
具體原理:
當遞歸函數被該裝飾器修飾后, 遞歸調用在裝飾器while循環內部進行, 每當產生新的遞歸調用棧幀時: f.f_back.f_back.f_code == f.f_code:, 就捕獲當前尾調用函數的參數, 并拋出異常, 從而銷毀遞歸棧并使用捕獲的參數手動調用遞歸函數. 所以遞歸的過程中始終只存在一個棧幀對象, 達到優化的目的.
不過這種企業需求真的很少!在python中遞歸深度最大是950+次(具體多少次真的不好說,不同的機子和平臺不一樣的!而且同一臺機子的不同執行時間也不一樣!這里就不去探究他!但是一定小于999次)
思否原文連接https://segmentfault.com/a/1190000007641519
關于尾遞歸: 當遞歸調用是函數體中最后執行的語句并且它的返回值不屬于表達式一部分時, 這個遞歸就是尾遞歸。
現代的編譯器就會發現這個特點, 生成優化的代碼, 復用棧幀。斐波那契數列算法中因為有個n * factorial(n-1) , 雖然也是遞歸,但是遞歸的結果處于一個表達式中,還要做計算, 所以就沒法復用棧幀了,只能一層一層的調用下去。
雖然尾遞歸調用也會創建新的棧, 但是我們可以優化使得尾遞歸的每一級調用共用一個棧!
關于尾遞歸優化還有另外一種思路,那就是漢諾塔思路:
具體查看網址https://www.cnblogs.com/tgycoder/p/6063722.html
相對于上面的算法,漢諾塔思路會更加好!但是漢諾塔思路設計的東西不會無限遞歸,雖然他和上面的拋出異常的方式都是尾遞歸的每一級調用共用一個棧,但是拋出異常的方式利用了cpython的機制 "漏洞" 來實現自己無限遞歸的運作方式!但是漢諾塔思路沒利用!
其中一種尾遞歸優化的方式其實就是建立在漢諾塔的思路上面的!比如下面的代碼:
def tail_recursion(n, total=0):
if n == 0:
return total
else:
return tail_recursion(n-1, total+n)##這一步必須沒有表達式,而是只有call本身函數
print(tail_recursion(993))##但是還是受到了cpython解釋器的限制!這里把993更改
##為999會發現拋出遞歸最大深度的異常