在python中装饰递归函数

时间:2012-05-25 16:09:22

标签: python recursion decorator

我很难理解装饰递归函数的工作原理。 对于以下代码段:

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)被递归调用时,它会自然地打印出'Original'。

第二个打印def_f(n),所以当n传递给包装器时,它会递归调用f(n)。但是包装器本身不是递归的,因此只打印一个“装饰”。

第三个让我感到困惑,这与使用装饰器@dec相同。为什么装饰f(n)也会调用包装器五次?在我看来,def_f = dec(f)和f = dec(f)只是绑定到两个相同函数对象的两个关键字。当装饰函数与未装饰的函数同名时,还有其他事情发生吗?

谢谢!

6 个答案:

答案 0 :(得分:5)

Python中的所有赋值只是将名称绑定到对象。当你有

f = dec(f)

您正在做的是将名称f绑定到dec(f)的返回值。此时,f不再引用原始函数。原始函数仍然存在并由新f调用,但您不再具有命名对原始函数的引用。

答案 1 :(得分:5)

正如你所说,第一个就像往常一样被召唤。

第二个在全局范围内放置一个名为dec_f的f的装饰版本。调用Dec_f,因此打印“Decorative!”,但在传递给dec的f函数内部,你调用f本身,而不是dec_f。查找名称f并在全局范围内找到它,它仍然在没有包装器的情况下定义,所以从而不会只调用f。

在3re示例中,您将装饰版本分配给名称f,因此当在函数f内部时,查找名称f,它在全局范围内查找,找到f,现在是装饰版本。< / p>

答案 2 :(得分:1)

你的函数调用一个名为f的东西,python在封闭范围内查找。

在语句f = dec(f)之前,f仍然绑定到未解包的函数,因此这就是被调用的内容。

答案 3 :(得分:1)

如果你做一点函数内省,打印出对应的内存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

答案 4 :(得分: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

答案 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对象。所以每次调用func里面的包装器都会调用原来的f,但f中的递归调用会调用f的装饰版本。

您只需在函数func.__name__

内打印f.__name__内部包装器和f即可实现此功能