函数装饰器和闭包
装饰器基础知识
-
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
@dacorate def target(): print("running target()")def target(): print("running target()") target = decorate(target) -
上述两种写法的最终结果一样,但执行完毕后得到的
target不一定是原来那个target函数,而是decorate(target)返回的函数。严格来说装饰器只是语法糖。其可以像常规的可调用对象那样调用,其参数是另一个函数。其一大特性是能把被装饰的函数替换成其他函数。第二个特性是,装饰器在加载模块时立即执行,并且函数装饰器在导入模块时立即执行,而被装饰的函数只有在明确调用时运行。registry = [] def register(func): print('running register(%s)' % func) registry.append(func) return func @register def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): print('running f3()') def main(): print('running main()') print('registry ->', registry) f1() f2() f3() if __name__ == '__main__': main() running register(<function f1 at 0x107bfb200>) running register(<function f2 at 0x107bfb3b0>) running main() registry -> [<function f1 at 0x107bfb200>, <function f2 at 0x107bfb3b0>] running f1() running f2() running f3()变量作用域规则
Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。
>>> b = 6 >>> def f2(a): ... print(a) ... print(b) // 在编译函数的定义体时,判断b为局部变量,但是b并没有绑定值。 ... b=9 ... >>> f2(3) 3 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in f2 UnboundLocalError: local variable 'b' referenced before assignment In [4]: b = 6 In [5]: def f3(a): ...: global b ...: print(a) ...: print(b) ...: b = 9 In [6]: f3(3) 3 6 In [7]: b Out[7]: 9闭包
闭包指延伸了作用域的函数,其中包含函数定义体中的引用,但不是在定义体中定义的非全局变量。它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
在此提到一个新名词:自由变量。自由变量有别于局部变量和全部变量,它指未在本地作用域中绑定的变量。在嵌套函数中使用了上级函数的局部变量,但并没有绑定新的值,那么这个变量便会成为自由变量。
自由变量绑定新值变为局部变量后可以使用
nonlocal再次标记为自由变量。
装饰器
一个简陋的装饰器
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args) #此处func为自由变量,并且为绑定新值,形成闭包。
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 snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n * factorial(n - 1)
# 等价于
# def factorial(n):
# return 1 if n < 2 else n * factorial(n - 1)
# factorial = clock(factorial)
# 所以说现factorial函数不再是原来的factorial函数,__name__,__doc__属性也发生改变。
# 并且这样写我们不能传入关键字参数。
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6))
print(factorial.__name__)
**************************************** Calling snooze(123)
[0.12405610s] snooze(.123) -> None
**************************************** Calling factorial(6)
[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
clocked
改造后的装饰器
import time
import functools
def clock(func):
@functools.wraps(func) #该装饰器可以把相关的属性从func复制到clocked中
def clocked(*args, **kwargs):
t0 = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(','.join(repr(arg) for arg in args))
if kwargs:
pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
arg_lst.append(", ".join(pairs))
arg_str = ', '.join(arg_lst)
print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
return result
return clocked
@clock
def factorial(n):
return 1 if n < 2 else n * factorial(n - 1)
# 等价于
# clocked = functools.wraps(clocked)
# factorial = clock(factorial)