前言 之前看廖雪峰老师的 Python 教程的时候卡在装饰器那一节,干脆跳了过去。
现在看到@property
的使用,又涉及到了装饰器的相关知识,于是只好回头来好好学一下了。。。
在了解装饰器之前,建议先了解一下什么是闭包
什么是装饰器 定义 装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
基础装饰器 这里使用两个实际例子来说明一下最基础的装饰器
日志打印 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def logger (func ): def wrapper (*args, **kw ): print ('Start function: {} ' .format (func.__name__)) func(*args, **kw) print ('End' ) return wrapper @logger def add (x, y ): print ('{} + {} = {}' .format (x, y, x+y)) add(2 ,3 )
计时器 计算一个函数的执行时长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import timedef timer (func ): def wrapper (*args, **kw ): start = time.time() time.sleep(1 ) func(*args, **kw) end = time.time() cost_time = end - start print ("Time spent:{}" .format (cost_time)) return wrapper @timer def add (x, y ): print ('{} + {} = {}' .format (x, y, x+y)) add(2 ,3 )
拿计时器这个例子来说
把@timer
放到了add
函数的定义,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰函数
或 装饰器
。
其实我们做到了这样一个实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import timedef timer (func ): def wrapper (*args, **kw ): start = time.time() time.sleep(1 ) func(*args, **kw) end = time.time() cost_time = end - start print ("Time spent:{}" .format (cost_time)) return wrapper def add (x, y ): print ('{} + {} = {}' .format (x, y, x+y)) timer(add)(2 ,3 )
timer(add)
返回了一个wrapper (*args, **kw)
函数,所以又加了一个(2,3)
传入wrapper
内供func()
调用,这里的func()
其实也就是add()
带参数的装饰器 如果要让装饰器带参数,那么需要两层的嵌套
打招呼 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def say_hello (country ): def wrapper (func ): def deco (*args, **kwargs ): if country == "china" : print ("你好!" ) elif country == "america" : print ('hello.' ) func(*args, **kwargs) return deco return wrapper @say_hello("china" ) def american (name ): print ("我来自中国,我叫" + name) @say_hello("america" ) def chinese (name ): print ("I am from America, and my name is " + name) american('张三' ) chinese('Alan' )
更高级的日志打印 我们现在要给每条日志加上level
,就像这样
1 2 [DEBUG] Log1 [WARN] Log2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def logger (level ): def wrapper (func ): def inner_wrapper (*args, **kw ): print (f'[{level} ] Start function {func.__name__} ' ) func(*args, **kw) print (f'[{level} ] End function {func.__name__} ' ) return inner_wrapper return wrapper @logger(level='DEBUG' ) def add (x, y ): print ('{} + {} = {}' .format (x, y, x+y)) add(2 ,3 )
在刚才的计时器例子中,如果你留心注意
1 2 >>> add.__name__ 'wrapper'
举个例子来看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import timedef timer (func ): def wrapper (*args, **kw ): start = time.time() func(*args, **kw) end = time.time() cost_time = end - start print ("Func:{1} Time spent:{0}" .format (cost_time, func.__name__)) return wrapper @timer def add (x, y ): print ('{} + {} = {}' .format (x, y, x+y)) print (add.__name__) add(2 ,3 )
这里我们打印了两次函数名,但是却出现了不同的结果。为什么?
我们使用装饰器后,
而timer
函数返回的就是wrapper
, 这样子的话其实就是
那么add
的 name 自然就是wrapper
的 name,所以出现了第二行的输出结果
我们执行add(2,3)
的时候,add
函数被作为func
传入timer
函数中,所以func.__name__
就是add.__name__
,所以输出的第三行就是add
不仅仅是__name__
, 其他的属性也会随之改变。
如何避免? 使用functools.wraps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import functoolsimport timedef timer (func ): @functools.wraps(func ) def wrapper (*args, **kw ): start = time.time() func(*args, **kw) end = time.time() cost_time = end - start print ("Func:{1} Time spent:{0}" .format (cost_time, func.__name__)) return wrapper @timer def add (x, y ): print ('{} + {} = {}' .format (x, y, x+y)) print (add.__name__) add(2 ,3 )
先更到这里吧…类装饰器之类的后续再更
参考文章
详解 Python 的装饰器 Toby Qin
https://www.cnblogs.com/cicaday/p/python-decorator.html
一篇文章搞懂装饰器所有用法 Ellison 张
https://www.cnblogs.com/ellisonzhang/p/11196390.html
对 python 函数后面有多个括号的理解?Python 探索牛
https://www.cnblogs.com/djdjdj123/p/12063737.html
装饰器 廖雪峰
https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584