简体   繁体   English

在python中装饰递归函数

[英]Decorating recursive functions in python

I am having hard time understanding how a decorated recursive function works.我很难理解装饰递归函数是如何工作的。 For the following snippet:对于以下代码段:

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))

The output is:输出是:

(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

The first one prints f(n) so naturally it prints 'Original' every time f(n) is called recursively.第一个打印 f(n) 很自然地,每次 f(n) 被递归调用时,它都会打印“原始”。

The second one prints def_f(n), so when n is passed to wrapper it calls f(n) recursively.第二个打印 def_f(n),因此当 n 传递给包装器时,它会递归调用 f(n)。 But the wrapper itself is not recursive so only one 'Decorated' is printed.但是包装器本身不是递归的,所以只打印一个“装饰”。

The third one puzzles me, which is the same as using decorator @dec.第三个让我很困惑,这与使用装饰器@dec 相同。 Why does decorated f(n) calls the wrapper five times also?为什么装饰 f(n) 也调用包装器五次? It looks to me that def_f=dec(f) and f=dec(f) are just two keywords bound to two identical function objects.在我看来, def_f=dec(f) 和 f=dec(f) 只是绑定到两个相同函数对象的两个关键字。 Is there something else going on when the decorated function is given the same name as the undecorated one?当装饰的函数与未装饰的函数同名时,是否还有其他事情发生?

Thanks!谢谢!

As you said, the first one is called as usual.正如你所说,第一个像往常一样被调用。

the second one puts a decorated version of f called dec_f in the global scope.第二个将 f 的修饰版本称为 dec_f 放在全局作用域中。 Dec_f is called, so that prints "Decorated!", but inside the f function passed to dec, you call f itself, not dec_f. Dec_f 被调用,因此打印“装饰!”,但在传递给 dec 的 f 函数内部,您调用 f 本身,而不是 dec_f。 the name f is looked up and found in the global scope, where it is still defined without the wrapper, so from than on, only f gets called.在全局范围内查找并找到名称 f,它仍然在没有包装器的情况下定义,因此从那时起,只有 f 被调用。

in the 3re example, you assign the decorated version to the name f, so when inside the function f, the name f is looked up, it looks in the global scope, finds f, which is now the decorated version.在 3re 示例中,您将修饰版本分配给名称 f,因此当在函数 f 内部查找名称 f 时,它会在全局范围内查找,找到 f,即现在的修饰版本。

All assignment in Python is just binding names to objects. Python 中的所有赋值只是将名称绑定到对象。 When you have当你有

f = dec(f)

what you are doing is binding the name f to the return value of dec(f) .您正在做的是将名称f绑定到dec(f)的返回值。 At that point, f no longer refers to the original function.此时, f不再指原始函数。 The original function still exists and is called by the new f , but you don't have a named reference to the original function anymore.原始函数仍然存在并由新的f调用,但您不再拥有对原始函数的命名引用。

Your function invokes something called f , which python looks up in the enclosing scope.你的函数调用了一个叫做f东西,python 在封闭的范围内查找它。

Until the statement f = dec(f) , f is still bound to the unwrapped function, so that's what is getting called.直到语句f = dec(f)f仍然绑定到解包函数,所以这就是被调用的。

If decorators indicate a prologue/epilogue to be done one before or after another function, we can avoid doing it several times simulating decorators with recursive functions.如果装饰指示序言/结尾是另一个函数之前或之后做了一个,我们能够避免这样做几次模拟递归函数的装饰。

For example:例如:

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)

Produces:产生:

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

We can simulate the decorator to force only one prologue/epilogue as:我们可以模拟装饰器只强制一个序言/尾声:

r = timing(fibonacci)(5)
print "Fibonacci %d of is %d" % (5,r)

Which produces:其中产生:

0.000010 seconds
Fibonacci 5 of is 5

if you do a bit of function introspection and print the corresponding memory id's, you will see that although the original function id is "baked" in the closure, it is in fact the closure function that is invoked inside the recursive如果你做一点函数内省并打印相应的内存 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))

if you run the code above, you will get如果你运行上面的代码,你会得到

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

Changed your code a bit稍微改变了你的代码

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))

I think this would make things bit more clear here, the wrapper function actually closes the func object from enclosing scope.我认为这会使这里的事情更清楚一些,包装函数实际上是从封闭范围内关闭 func 对象。 So every call to func inside wrapper would call the original f but the recursive call within f will call the decorated version of f.所以每次调用 func 在包装器中都会调用原始的 f 但 f 中的递归调用将调用 f 的修饰版本。

You can actually see this by simply printing the func.__name__ inside wrapper and f.__name__ inside the function f您实际上可以通过简单地在包装器内打印func.__name__和在函数f内打印f.__name__来看到这一点

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM