2012-05-25 38 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