【问题标题】:Decorating recursive functions in python在python中装饰递归函数
【发布时间】:2012-06-01 05:45:50
【问题描述】:

我很难理解修饰的递归函数是如何工作的。 对于以下sn-p:

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

谢谢!

【问题讨论】:

  • 对原始f 函数的引用仍然存在,因此调用了那个。当您执行f = dec(f) 时,您将始终调用新函数。而新的函数会调用原来的函数。
  • decorator 可能不是在这里使用的正确术语,因为您实际上从未将装饰器应用于函数。您对f = dec(f) 的最后一次测试几乎(如果不完全)与@dec def f 相同

标签: python recursion decorator


【解决方案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

【讨论】:

    【解决方案2】:

    稍微修改了代码

    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__ 来看到这一点

    【讨论】:

      【解决方案3】:

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

      例如:

      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
      

      【讨论】:

      • 真的很好,最好能提供一些解释。
      • 有趣的是,这正是我想要做的,为斐波那契写一个时序装饰器。谢谢!
      • 令人惊讶的是,使用现代@ 来装饰递归函数的行为很有趣,因此实际修复它的方法是使用旧方法,即。 decorator_func(my_func)(args)
      【解决方案4】:

      如你所说,第一个照常调用。

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

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

      【讨论】:

      • 谢谢!这就是我一直在寻找的。所以问题出在 def f 中的 return(f(n-1)+n) 语句,其中 f(n-1) 现在是新的 dec(f)。
      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

        f = dec(f)
        

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

        【讨论】:

          猜你喜欢
          • 2022-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-05-20
          • 2020-01-05
          • 1970-01-01
          相关资源
          最近更新 更多