16

我很难理解修饰的递归函数是如何工作的。对于以下代码段:

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) 只是绑定到两个相同函数对象的两个关键字。当装饰函数与未装饰函数的名称相同时,是否还有其他事情发生?

谢谢!

4

6 回答 6

9

正如你所说,第一个像往常一样被调用。

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

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

于 2012-05-25T16:19:02.370 回答
5

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

f = dec(f)

您正在做的是将名称绑定fdec(f). 至此,f不再指代原来的功能。原始函数仍然存在并由 new 调用f,但您不再有对原始函数的命名引用。

于 2012-05-25T16:13:59.187 回答
2

如果装饰器指示要在另一个函数之前或之后完成序言/尾声我们可以避免多次使用递归函数模拟装饰器。

例如:

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
于 2016-10-23T18:12:29.400 回答
1

你的函数调用了一个叫做 的东西f,python 在封闭范围内查找它。

直到 statement f = dec(f),f仍然绑定到展开的函数,所以这就是被调用的。

于 2012-05-25T16:15:13.747 回答
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
于 2021-01-27T14:49:41.973 回答
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

于 2017-08-10T08:19:38.577 回答