2012-05-25 9 views
7

装飾された再帰関数がどのように機能するかを理解するのに苦労しています。 次のスニペットの場合:Pythonで再帰関数をデコレートする

def dec(f): 
    def wrapper(*argv): 
     print(argv, 'Decorated!') 
     return(f(*argv)) 
    return(wrapper) 

def f(n): 
    print(n, 'Original!') 
    if n == 1: return(1) 
    else: return(f(n - 1) + n) 

print(f(5)) 
print 

dec_f = dec(f) 
print(dec_f(5)) 
print 

f = dec(f) 
print(f(5)) 

出力は次のとおり

(5, 'Original!') 
(4, 'Original!') 
(3, 'Original!') 
(2, 'Original!') 
(1, 'Original!') 
15 

((5,), 'Decorated!') 
(5, 'Original!') 
(4, 'Original!') 
(3, 'Original!') 
(2, 'Original!') 
(1, 'Original!') 
15 

((5,), 'Decorated!') 
(5, 'Original!') 
((4,), 'Decorated!') 
(4, 'Original!') 
((3,), 'Decorated!') 
(3, 'Original!') 
((2,), 'Decorated!') 
(2, 'Original!') 
((1,), 'Decorated!') 
(1, 'Original!') 
15 

最初のプリントfは(N)が自然にそれが 'オリジナル' たびに、F(n)が再帰的に呼び出される印刷します。

2つ目はdef_f(n)を出力するので、nがラッパーに渡されるとf(n)が再帰的に呼び出されます。しかし、ラッパー自体は再帰的ではないので、「飾られた」の1つだけが印刷されます。

デコレータ@decを使用するのと同じ3番目のものが私を困惑させます。なぜ装飾されたf(n)もラッパーを5回呼び出すのですか? def_f = dec(f)とf = dec(f)は、2つの同一の関数オブジェクトにバインドされた2つのキーワードに過ぎないと思う。装飾された機能に装飾されていない機能と同じ名前が付けられているときには、何か他のことがありますか?

ありがとうございます!

+1

元 'F'関数への参照がまだ存在しているの内側ラッパー内部func.__name__f.__name__を印刷することで、これを見ることができます。 'f = dec(f)'を実行すると、常に新しい関数が呼び出されます。そして、新しい関数はオリジナルを呼び出します。 – JBernardo

+0

'decorator'は実際にデコレータをこの関数に適用することはないので、ここで使用する正しい用語ではないかもしれません。 'f = dec(f)'の最後のテストは '@dec def f'とほとんど同じです(正確でない場合) –

答えて

4

あなたが言ったように、最初のものはいつものように呼び出されます。

第2のものは、dec_fというfの装飾されたバージョンをグローバルスコープに置く。 Dec_fが呼び出されるので、 "Decorated!"が出力されますが、decに渡されたf関数の内部では、dec_fではなくf自身が呼び出されます。名前fはグローバルスコープ内で検索され、ラッパーなしでまだ定義されているので、fからのみが呼び出されます。

3つの例では、修飾されたバージョンを名前fに割り当てます。したがって、関数fの内部で名前fが検索されると、グローバルスコープ内を検索してfを見つけます。デコレータは、プロローグ/エピローグが他の関数の前または後にを行う必要が示している場合

+0

ありがとう!これは私が探していたものです。だから問題は、f(n-1)が今度は新しいdec(f)であるdef fのreturn(f(n-1)+ n)文です。 – jianglai

5

Pythonでのすべての割り当ては、オブジェクトへの名前のバインドに過ぎません。あなたは何をしているか

f = dec(f) 

を持っている場合dec(f)の戻り値に名前fを結合です。その時点で、fは元の関数を参照しなくなりました。元の関数はまだ存在し、新しいfによって呼び出されますが、という名前の元の関数をもう参照していないがありません。

1

あなたの関数は、fと呼ばれるものを呼び出します。これは、pythonが囲みスコープ内を検索します。

f = dec(f)まで、fは未ラップ関数にバインドされているので、これが呼び出されます。

0

、我々はそれを数回再帰関数とシミュレートするデコレータを行うことを避けることができます。例えば

def timing(f): 
    def wrapper(*args): 
     t1 = time.clock(); 
     r = apply(f,args) 
     t2 = time.clock(); 
     print"%f seconds" % (t2-t1) 
     return r 
    return wrapper 

@timing 
def fibonacci(n): 
    if n==1 or n==2: 
     return 1 
    return fibonacci(n-1)+fibonacci(n-2) 

r = fibonacci(5) 
print "Fibonacci of %d is %d" % (5,r) 

が生成されます

0.000000 seconds 
0.000001 seconds 
0.000026 seconds 
0.000001 seconds 
0.000030 seconds 
0.000000 seconds 
0.000001 seconds 
0.000007 seconds 
0.000045 seconds 
Fibonacci of 5 is 5 

我々は唯一のプロローグ/エピローグを強制的にデコレータをシミュレートすることができます。

生成
r = timing(fibonacci)(5) 
print "Fibonacci %d of is %d" % (5,r) 

0.000010 seconds 
Fibonacci 5 of is 5 
0

は、私は、これは物事がラッパー関数は、実際にスコープを囲むからFUNCオブジェクトをクローズし、ここでよりクリアビットになるだろうと思いますあなたのコードビット

def dec(func): 
    def wrapper(*argv): 
     print(argv, 'Decorated!') 
     return(func(*argv)) 
    return(wrapper) 

def f(n): 
    print(n, 'Original!') 
    if n == 1: return(1) 
    else: return(f(n - 1) + n) 

print(f(5)) 
print 

dec_f = dec(f) 
print(dec_f(5)) 
print 

f = dec(f) 
print(f(5)) 

を変更しました。だから、ラッパーの中のfuncを呼び出すたびに元のfが呼び出されますが、f内の再帰呼び出しはfの装飾されたバージョンを呼び出します。

あなたが実際に単にので、その1が呼ばれ、機能f