python中,函数可以像变量一样当作参数传递给另一个函数, 装饰器则沿用了这一特性,在不改变既有代码的前提下,增加功能。
描述
装饰器本质上是一个 Python 函数或类,返回值也是一个函数/类对象。
原则:
- 不修改被修饰函数的源代码
- 不修改被修饰函数的调用方式
装饰器 = 高阶函数+函数嵌套+闭包
高阶函数
函数接受参数为函数名或函数返回值是函数名
1 2 3 4
| def add(x, y, f): return f(x) + f(y) add(-5,6,abs)
|
函数嵌套
函数内部定义函数
1 2 3 4 5 6 7
| def outer(): def inner(): print("inner") print("outer") inner() outer()
|
闭包
闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。
通常函数被调用后,局部变量变失去作用域,闭包可以避免使用全局变量,使得变量脱离了函数本身的作用范围,局部变量依旧可以被访问得到
1 2 3 4 5 6 7 8 9 10
| def adder(x): def wrapper(y): return x + y return wrapper adder5 = adder(5)
adder5(10)
adder5(6)
|
简单装饰器
函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程。在java Spring框架aop就是面向切面编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import time
def timmer(func): def wrapper(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) stop_time = time.time() print(f'运行时长{stop_time-start_time}') return res return wrapper
def IO_operation(): time.sleep(4)
IO_operation = timmer(IO_operation) IO_operation()
|
@ 语法糖 简化调用
1 2 3 4 5
| @timmer def IO_operation(): time.sleep(4)
IO_operation()
|
带参数的装饰器
装饰器的语法允许我们在调用时,提供其它参数,它实际上是对原有装饰器的一个函数封装,并返回一个装饰器.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| def timmer(level): def decorator(func): def wrapper(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) stop_time = time.time() if level=1: print(f'1:{stop_time-start_time}') if level=2: print(f'2:{stop_time-start_time}') return res return wrapper return decorator def IO_operation(): time.sleep(4) @timmer(1) def IO_operation(): time.sleep(4)
IO_operation()
|
类装饰器
类装饰器主要依靠类的__call__方法,当一个类如果实现了__call__方法,那么该类的实例对象的行为就是一个函数,是一个可以被调用(callable)的对象。
1 2 3 4 5 6 7 8 9
| class Add: def __init__(self, n): self.n = n def __call__(self, x): return self.n + x
>>> add = Add(1) >>> add(4) >>> 5
|
确定对象是否为可调用对象通过内置函数callable来判断
1 2 3 4 5 6
| >>> callable(foo) True >>> callable(1) False >>> callable(int) True
|
类装饰器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Foo(object): def __init__(self, func): self._func = func
def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending')
@Foo def bar(): print ('bar')
bar()
|
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息被内部函数得元信息替代,python内部提供functools.wraps装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。
1 2 3 4 5 6 7 8 9 10 11 12 13
| from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ print func.__doc__ return func(*args, **kwargs) return with_logging
@logged def f(x): """does some math""" return x + x * x
|
装饰器顺序
一个函数还可以同时定义多个装饰器,比如:
1 2 3 4 5 6 7
| @a @b @c def f (): pass >>>f = a(b(c(f)))
|
参考:
https://foofish.net/python-closure.html
https://foofish.net/python-decorator.html
https://foofish.net/function-is-first-class-object.html