我很难理解装饰递归函数的工作原理。 对于以下代码段:
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)只是绑定到两个相同函数对象的两个关键字。当装饰函数与未装饰的函数同名时,还有其他事情发生吗?
谢谢!
答案 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
即可实现此功能