2012-05-25 64 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)被遞歸調用。

第二個打印def_f(n),所以當n傳遞給包裝時,它會遞歸地調用f(n)。但包裝本身不是遞歸的,所以只打印一個'裝飾'。

第三個令我困惑,這和使用裝飾者@dec一樣。爲什麼裝飾的f(n)也會調用wrapper五次?在我看來,def_f = dec(f)和f = dec(f)只是綁定到兩個相同函數對象的兩個關鍵字。當裝飾的功能與未修飾的功能具有相同的名稱時,是否還有其他內容?

謝謝!

+1

參考原來的'F'功能仍然存在裏面,這樣一個被稱爲看到這一點。當你做'f = dec(f)'時,你將總是調用新的函數。新功能將調用原始文件。 – JBernardo

+0

'裝飾器'可能不是在這裏使用的正確術語,因爲你從來沒有真正將裝飾器應用到函數中。你最後一次測試'f = dec(f)'與'@dec def f'差不多(如果不完全一樣) –

回答

4

正如你所說,第一個是像往常一樣。

第二個在全局範圍內放置一個名爲dec_f的f的裝飾版本。 Dec_f被調用,所以打印出「Decorated!」,但在f函數內部傳遞給dec,你調用f本身,而不是dec_f。名稱f在全局範圍內查找並找到,它仍然在沒有包裝的情況下定義,因此從f開始只調用f。在3re的例子中,你將裝飾版本分配給名字f,所以當在函數f中查找名稱f時,它在全局範圍中查找,找到f,它現在是裝飾版本。

+0

謝謝!這是我正在尋找的。所以問題是def f中的返回(f(n-1)+ n)語句,其中f(n-1)現在是新的dec(f)。 – jianglai

5

Python中的所有賦值都只是將名稱綁定到對象。當你有

f = dec(f) 

你在做什麼的名稱是f結合的dec(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

改變了你的代碼有點

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的裝飾版本。

實際上,你可以通過簡單的印刷包裝內的func.__name__f.__name__功能f