簡體   English   中英

在python中裝飾遞歸函數

[英]Decorating recursive functions in 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) 也調用包裝器五次? 在我看來, def_f=dec(f) 和 f=dec(f) 只是綁定到兩個相同函數對象的兩個關鍵字。 當裝飾的函數與未裝飾的函數同名時,是否還有其他事情發生?

謝謝!

正如你所說,第一個像往常一樣被調用。

第二個將 f 的修飾版本稱為 dec_f 放在全局作用域中。 Dec_f 被調用,因此打印“裝飾!”,但在傳遞給 dec 的 f 函數內部,您調用 f 本身,而不是 dec_f。 在全局范圍內查找並找到名稱 f,它仍然在沒有包裝器的情況下定義,因此從那時起,只有 f 被調用。

在 3re 示例中,您將修飾版本分配給名稱 f,因此當在函數 f 內部查找名稱 f 時,它會在全局范圍內查找,找到 f,即現在的修飾版本。

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

f = dec(f)

您正在做的是將名稱f綁定到dec(f)的返回值。 此時, f不再指原始函數。 原始函數仍然存在並由新的f調用,但您不再擁有對原始函數的命名引用。

你的函數調用了一個叫做f東西,python 在封閉的范圍內查找它。

直到語句f = dec(f)f仍然綁定到解包函數,所以這就是被調用的。

如果裝飾指示序言/結尾是另一個函數之前或之后做了一個,我們能夠避免這樣做幾次模擬遞歸函數的裝飾。

例如:

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

如果你做一點函數內省並打印相應的內存 id,你會看到雖然原始函數 id 是在閉包中“烘焙”的,但實際上是在遞歸內部調用的閉包函數

def dec(func):
    def wrapper(*argv):
        print(argv, 'Decorated!')
        print("func id inside wrapper= ", hex(id(func)))
        return(func(*argv))
    return(wrapper)

def f(n):
    print(n, 'Original!')
    print("f id inside recursive function = ", hex(id(f)))
    if n == 1: return(1)
    else: return(f(n - 1) + n)

orig_id = hex(id(f))
print("recursive f id after its definition = ", orig_id)

f = dec(f)
closure_id = hex(id(f))
print("id of the closure = ", closure_id)

print("function object is at {0}".format(orig_id), f.__closure__)

print(f(1))

如果你運行上面的代碼,你會得到

recursive f id after its definition =  0x1ce45be19d0
id of the closure =  0x1ce45c49a60
function object is at 0x1ce45be19d0 (<cell at 0x000001CE45AFACD0: function object at 0x000001CE45BE19D0>,)
(1,) Decorated!
func id inside wrapper=  0x1ce45be19d0
1 Original!
f id inside recursive function =  0x1ce45c49a60
1

稍微改變了你的代碼

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 對象。 所以每次調用 func 在包裝器中都會調用原始的 f 但 f 中的遞歸調用將調用 f 的修飾版本。

您實際上可以通過簡單地在包裝器內打印func.__name__和在函數f內打印f.__name__來看到這一點

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM