デコレータを使用して Python で末尾再帰を最適化する

高洛峰
リリース: 2017-03-02 10:55:03
オリジナル
1674 人が閲覧しました

ここでは、例として典型的なフィボナッチ数列を使用して、デコレーターを使用して Python で末尾再帰を最適化する方法を示します。必要な方は、

末尾再帰の概要
末尾再帰とは、関数が最後の操作を返すときのことです。が再帰呼び出しである場合、関数は末尾再帰です。
再帰は線形です。たとえば、階乗関数が呼び出されるたびに新しいスタック (後入れ先出し) が作成され、スタックを継続的にプッシュすることで再帰が発生し、スタック オーバーフローが発生しやすくなります。末尾再帰は、現在のスタックを使用して、データ カバレッジを通じて再帰関数を最適化します。
階乗関数factorialは、計算された値を渡すことによって末尾再帰を完了します。ただし、Python ではコンパイラーが末尾再帰を最適化できないため、再帰が複数回繰り返された場合でも、(学習目的で) エラーが報告されます。

例:

def factorial(n, x):
  if n == 0:
    return x
  else:
    return factorial(n-1, n*x)

print factorial(5, 1) # 120
ログイン後にコピー

末尾再帰最適化
線形再帰アルゴリズムは非効率すぎるため、ここでは例として使用されています。まず、 の呼び出しを見てみましょう。テールパスメソッド:

(n,b1=1,b2=1,c=3):
 if n<3:
  return 1
 else:
  if n==c:
   return b1+b2
  else:
   return Fib(n,b1=b2,b2=b1+b2,c=c+1)
ログイン後にコピー

このプログラムをテストして Fib(1001) を呼び出してみましょう。結果は次のようになります:

>>> def Fib(n,b1=1,b2=1,c=3):

...  if n<3:

...   return 1

...  else:

...   if n==c:

...    return b1+b2

...   else:

...    return Fib(n,b1=b2,b2=b1+b2,c=c+1)

... 

>>> Fib(1001)

70330367711422815821835254877183549770181269836358732742604905087154537118196933579742249494562611733487750449241765991088186363265450223647106012053374121273867339111198139373125598767690091902245245323403501L

>>>
ログイン後にコピー

Fib(1002) を使用すると、結果はコーヒーテーブルになります。

 .....

 File "<stdin>", line 8, in Fib

 File "<stdin>", line 8, in Fib

 File "<stdin>", line 8, in Fib

 File "<stdin>", line 8, in Fib

 File "<stdin>", line 8, in Fib

 File "<stdin>", line 8, in Fib

RuntimeError: maximum recursion depth exceeded

>>>
ログイン後にコピー

さて、末尾再帰最適化に移ります

今、次のように Fib 関数に Decorator を追加します:

@tail_call_optimized
def Fib(n,b1=1,b2=1,c=3):
 if n<3:
  return 1
 else:
  if n==c:
   return b1+b2
  else:
   return Fib(n,b1=b2,b2=b1+b2,c=c+1)
ログイン後にコピー


さて、これは@tail_call_optimized の装飾 このデコレーターにより、Python は呼び出しスタックの制限を魔法のように突破できます。

これで、Fib (20000) を使用した場合でも、結果を 780 ミリ秒で実行できます (780 ミリ秒は、前のブログ投稿で言及した 2000 元のネットブックの結果です)

早速、この魔法のコードを見てみましょう。 :

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&#39;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, e: 
   args = e.args 
   kwargs = e.kwargs 
 func.__doc__ = g.__doc__ 
 return func
ログイン後にコピー

が使用したメソッドは、以前にも紹介しましたが、著者が例外をスローし、それを自分でキャッチして呼び出しスタックの増大を阻止するというメソッドを使用したことです。ただただ信じられないほどです。さらに、効率の問題により、直接末尾再帰 Fib と比較して約 5 倍の時間オーバーヘッドが発生します。

最終的には、驚くべきことに、末尾再帰最適化の目標は達成されました。


Python での末尾再帰を最適化するためのデコレーターの使用に関連するその他の記事については、PHP 中国語 Web サイトに注目してください。


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート