[英]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.