[英]how a decorator works when the argument is recursive function?
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
this is the decorator. 这是装饰工。
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
the parts of result is: 结果的部分是:
[0.00000191s] factorial(1) -> 1
[0.00004911s] factorial(2) -> 2
[0.00008488s] factorial(3) -> 6
[0.00013208s] factorial(4) -> 24
[0.00019193s] factorial(5) -> 120
[0.00026107s] factorial(6) -> 720
6! = 720
how this decorator works when the argument is recursive function? 当参数是递归函数时,此装饰器如何工作? why the decorator can be executed for many times. 为什么装饰器可以执行多次。 how it works? 这个怎么运作?
In your example, the clock
decorator is executed once, when it replaces the original version of factorial
with the clocked version. 在您的示例中, clock
装饰器在将clock
factorial
替换原始版本的factorial
时执行一次。 The original factorial
is recursive and therefore the decorated version is recursive too. 原始factorial
是递归的,因此修饰后的版本也是递归的。 And so you get the timing data printed for each recursive call - the decorated factorial
calls itself, not the original version, because the name factorial
now refers to the decorated version. 这样,您就可以为每个递归调用打印时序数据-装饰的factorial
调用本身而不是原始版本,因为名称factorial
现在是指装饰的版本。
It's a good idea to use functools.wraps
in decorators. 在装饰器中使用functools.wraps
是一个好主意。 This copies various attributes of the original function to the decorated version. 这会将原始功能的各种属性复制到修饰的版本。
For example, without wraps
: 例如,没有wraps
:
import time
def clock(func):
def clocked(*args):
''' Clocking decoration wrapper '''
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
@clock
def factorial(n):
''' Recursive factorial '''
return 1 if n < 2 else n * factorial(n-1)
print(factorial.__name__, factorial.__doc__)
output 输出
clocked Clocking decoration wrapper
With wraps
: 带wraps
:
import time
from functools import wraps
def clock(func):
@wraps(func)
def clocked(*args):
''' Clocking decoration wrapper '''
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
@clock
def factorial(n):
''' Recursive factorial '''
return 1 if n < 2 else n * factorial(n-1)
print(factorial.__name__, factorial.__doc__)
output 输出
factorial Recursive factorial
which is what we'd get if we did print(factorial.__name__, factorial.__doc__)
on the undecorated version. 如果我们在未修饰的版本上print(factorial.__name__, factorial.__doc__)
,将会得到什么。
If you don't want the clock
-decorated recursive function to print the timing info for all of the recursive calls, it gets a bit tricky. 如果您不希望clock
装饰的递归函数为所有递归调用打印时序信息,则它会有些棘手。
The simplest way is to not use the decorator syntax and just call clock
as a normal function so we get a new name for the clocked version of the function: 最简单的方法是不使用装饰器语法,而只是将clock
当作普通函数调用,因此我们为函数的时钟版本获得了新的名称:
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
clocked_factorial = clock(factorial)
for n in range(7):
print('%d! = %d' % (n, clocked_factorial(n)))
output 输出
[0.00000602s] factorial(0) -> 1
0! = 1
[0.00000302s] factorial(1) -> 1
1! = 1
[0.00000581s] factorial(2) -> 2
2! = 2
[0.00000539s] factorial(3) -> 6
3! = 6
[0.00000651s] factorial(4) -> 24
4! = 24
[0.00000742s] factorial(5) -> 120
5! = 120
[0.00000834s] factorial(6) -> 720
6! = 720
Another way is to wrap the recursive function in a non-recursive function and apply the decorator to the new function. 另一种方法是将递归函数包装在非递归函数中,并将装饰器应用于新函数。
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
@clock
def nr_factorial(n):
return factorial(n)
for n in range(3, 7):
print('%d! = %d' % (n, nr_factorial(n)))
output 输出
[0.00001018s] nr_factorial(3) -> 6
3! = 6
[0.00000799s] nr_factorial(4) -> 24
4! = 24
[0.00000801s] nr_factorial(5) -> 120
5! = 120
[0.00000916s] nr_factorial(6) -> 720
6! = 720
Another way is to modify the decorator so that it keeps track of whether it's executing the top level of the recursion or one of the inner levels, and only print the timing info for the top level. 另一种方法是修改装饰器,以使其跟踪是执行递归的顶级还是内部级别之一,并且仅打印顶级的计时信息。 This version uses the nonlocal
directive so it only works in Python 3, not Python 2. 此版本使用nonlocal
指令,因此仅适用于Python 3,不适用于Python 2。
def rclock(func):
top = True
@wraps(func)
def clocked(*args):
nonlocal top
if top:
top = False
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
else:
result = func(*args)
top = True
return result
return clocked
@rclock
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
for n in range(3, 7):
print('%d! = %d' % (n, factorial(n)))
output 输出
[0.00001253s] factorial(3) -> 6
3! = 6
[0.00001205s] factorial(4) -> 24
4! = 24
[0.00001227s] factorial(5) -> 120
5! = 120
[0.00001422s] factorial(6) -> 720
6! = 720
The rclock
function can be used on non-recursive functions, but it's a little more efficient to just use the original version of clock
. rclock
函数可以用于非递归函数,但是仅使用原始版本的clock
更有效率。
Another handy function in functools
that you should know about if you're using recursive functions is lru_cache
. 如果您正在使用递归函数, functools
中应该知道的另一个便捷函数是lru_cache
。 This keeps a cache of recently computed results so they don't need to be re-computed. 这样可以保留最近计算的结果的缓存,因此不需要重新计算它们。 This can enormously speed up recursive functions. 这可以极大地加速递归函数。 Please see the docs for details. 请参阅文档以获取详细信息。
We can use lru_cache
in conjunction with clock
or rclock
. 我们可以将lru_cache
与clock
或rclock
结合使用。
@lru_cache(None)
@clock
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
for n in range(3, 7):
print('%d! = %d' % (n, factorial(n)))
output 输出
[0.00000306s] factorial(1) -> 1
[0.00017850s] factorial(2) -> 2
[0.00022049s] factorial(3) -> 6
3! = 6
[0.00000542s] factorial(4) -> 24
4! = 24
[0.00000417s] factorial(5) -> 120
5! = 120
[0.00000409s] factorial(6) -> 720
6! = 720
As you can see, even though we used the plain clock
decorator only a single line of timing info gets printed for the factorials of 4, 5, and 6 because the smaller factorials are read from the cache instead of being re-computed. 如您所见,即使我们使用普通clock
装饰器,也只为4、5和6的阶乘打印了一行定时信息,因为较小的阶乘是从缓存读取的,而不是重新计算的。
When you apply a decorator to a function, the function is passed as a parameter to the decorator. 将装饰器应用于函数时,该函数将作为参数传递给装饰器。 Whether the function is recursive or not does not matter. 该函数是否递归无关紧要。
The code 编码
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
is equivalent to 相当于
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
factorial = clock(factorial)
The decorated function is passed to the decorator as an argument and returned another function to replace the original one, the returned function is no recursive function, but when you call it, it'll call the original recursive function: 装饰函数作为参数传递给装饰器,并返回另一个函数来替换原始函数,返回的函数不是递归函数,但是当您调用它时,它将调用原始递归函数:
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args) # You call your original recursive function here
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
When you call your decorated function factorial
, what you actually called is clocked
, and it actually call factorial
in the following line: 当您调用修饰的函数factorial
,实际调用的是clocked
,它实际上在以下行中调用factorial
:
result = func(*args)
The decorator is executed only once. 装饰器仅执行一次。
For understanding, you can think your function becomes to the following one after @clock
: 为了理解,您可以认为您的函数在@clock
之后变为以下函数:
def factorial(*args):
def _factorial(n):
return 1 if n < 2 else n*_factorial(n-1)
t0 = time.perf_counter()
result = _factorial(*args)
elapsed = time.perf_counter() - t0
name = _factorial.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
Maybe it helps to assume the "syntactic sugar" point of view. 也许有助于假设“句法糖”的观点。 This is from PEP 318 with modifications (I simplified the example) 这是来自PEP 318的修改(我简化了示例)
The current syntax for function decorators as implemented in Python 2.4a2 is: 在Python 2.4a2中实现的函数装饰器的当前语法为:
@dec
def func(arg1, arg2, ...):
pass
This is equivalent to: 这等效于:
def func(arg1, arg2, ...):
pass
func = dec(func)
As you can see the decorator function is called only once and the wrapper it returns is assigned to the name of the decorated function. 如您所见,装饰器函数仅被调用一次,并且返回的包装器被分配给装饰函数的名称。
Thus whenever the original function is called by its name (for example in a recursion) the wrapper (but not the decorator function) is called instead. 因此,无论何时以其名称(例如,在递归中)调用原始函数时,都将调用包装器(而不是装饰器函数)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.