装饰器其实一直是我的一个"老大难"。这个知识点就放在那,但是拖延症。。。
其实在平常写写脚本的过程中,这个知识点你可能用到不多
但在面试的时候,这可是一个高频问题。
一、什么是装饰器
所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。
这一句话理解起来可能没那么轻松,那先来看一个"傻瓜"函数。
放心,绝对不是"Hello World"!
def hello():
print("你好,装饰器")
加python学习qq群:775690737 送python零基础入门学习资料+99个源码
肿么样,木骗你吧? 哈哈,这个函数不用运行相信大家都知道输出结果: "你好,装饰器"
。
那如果我想让 hello()
函数再实现个其他功能,比如多打印一句话。
那么,可以这样"增强"一下:
def my_decorator(func):
def wrapper():
print("这是装饰后具有的新输出")
func()
return wrapper
def hello():
print("你好,装饰器")
hello = my_decorator(hello)
hello()
加python学习qq群:775690737 送python零基础入门学习资料+99个源码
运行结果:
这是装饰后具有的新输出 你好,装饰器 [Finished in 0.1s]
很显然,这个"增强"没啥作用,但是可以帮助理解装饰器。
当运行最后的 hello()
函数时,调用过程是这样的:
hello = my_decorator(hello)
中,变量hello指向的是my_decorator()
my_decorator(func)
中返回的wrapper()
,传参是hello
,因此又会调用到原函数hello()
- 于是乎,先打印出了
wrapper()
函数里的,然后才打印出hello()
函数里的
那上述代码里的 my_decorator()
就是一个装饰器。
它改变了 hello()
的行为,但是并没有去真正的改变 hello()函数
的内部实现。
但是,python一直以"优雅"被人追捧,而上述的代码显然不够优雅。
二、优雅的装饰器
所以,想让上述装饰器变得优雅,可以这样写:
d
ef my_decorator(func):
def wrapper():
print("这是装饰后具有的新输出")
func()
return wrapper
@my_decorator
def hello():
print("你好,装饰器")
hello()
加python学习qq群:775690737 送python零基础入门学习资料+99个源码
这里的 @my_decorator
就相当于旧代码的 hello = my_decorator(hello)
, @
符号称为语法糖。
那如果还有其他函数也需要加上类似的装饰,直接在函数的上方加上 @my_decorator
就可以,大大提高函数
的重复利用与可读性。
def my_decorator(func):
def wrapper():
print("这是装饰后具有的新输出")
func()
return wrapper
@my_decorator
def hello():
print("你好,装饰器")
@my_decorator
def hello2():
print("你好,装饰器2")
hello2()
加python学习qq群:775690737 送python零基础入门学习资料+99个源码
输出:
这是装饰后具有的新输出 你好,装饰器2 [Finished in 0.1s]
三、带参数的装饰器
1. 单个参数
上面的只是一个非常简单的装饰器,但是实际场景中,很多函数都是要带有参数的,比如hello(people_name)。
其实也很简单,要什么我们就给什么呗,直接在对应装饰器的 wrapper()
上,加上对应的参数:
def my_decorator(func):
def wrapper(people_name):
print("这是装饰后具有的新输出")
func(people_name)
return wrapper
@my_decorator
def hello(people_name):
print("你好,{}".format(people_name))
hello("张三")
加python学习qq群:775690737 送python零基础入门学习资料+99个源码
输出:
这是装饰后具有的新输出 你好,张三 [Finished in 0.1s]
2. 多个参数
但是还没完,这样虽然简单,但是随之而来另一个问题:因为并不是所有函数参数都是一样的,
当其他要使用装饰器的函数参数不止这个一个肿么办?比如:
@my_decorator
def hello3(speaker, listener):
print("{}对{}说你好!".format(speaker, listener))
加python学习qq群:775690737 送python零基础入门学习资料+99个源码
没关系,在python里, *args
和 **kwargs
表示接受任意数量和类型的参数,所以我们可以这样
写装饰器里的 wrapper()
函数:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("这是装饰后具有的新输出")
func(*args, **kwargs)
return wrapper
@my_decorator
def hello(people_name):
print("你好,{}".format(people_name))
@my_decorator
def hello3(speaker, listener):
print("{}对{}说你好!".format(speaker, listener))
hello("老王")
print("------------------------")
hello3("张三", "李四")
加python学习qq群:775690737 送python零基础入门学习资料+99个源码
同时运行下 hello("老王")
,和 hello3("张三", "李四")
,看结果:
这是装饰后具有的新输出 你好,老王 ------------------------ 这是装饰后具有的新输出 张三对李四说你好! [Finished in 0.1s]
3. 自定义参数
上面2种,装饰器都是接收外来的参数,其实装饰器还可以接收自己的参数。
比如,我加个参数来控制下装饰器中打印信息的次数:
def count(num): def my_decorator(func): def wrapper(*args, **kwargs): for i in range(num): print("这是装饰后具有的新输出") func(*args, **kwargs) return wrapper return my_decorator @count(3) def hello(people_name): print("你好,{}".format(people_name)) hello("老王")
注意,这里 count
装饰函数中的2个 return
.
运行下,应该会出现3次:
这是装饰后具有的新输出 你好,老王 这是装饰后具有的新输出 你好,老王 这是装饰后具有的新输出 你好,老王 [Finished in 0.1s]
4. 内置装饰器 @functools.wrap
现在多做一步探索,我们来打印下下面例子中的hello()函数的元信息:
def my_decorator(func): def wrapper(*args, **kwargs): print("这是装饰后具有的新输出") func(*args, **kwargs) return wrapper @my_decorator def hello(people_name): print("你好,{}".format(people_name)) print(hello.__name__) #看下hello函数的元信息
输出:
wrapper
这说明了,它不再是以前的那个 hello()
函数,而是被 wrapper()
函数取代了。
如果我们需要用到元函数信息,那怎么保留它呢?这时候可以用内置装饰器 @functools.wrap
。
import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print("这是装饰后具有的新输出") func(*args, **kwargs) return wrapper @my_decorator def hello(people_name): print("你好,{}".format(people_name)) print(hello.__name__)
运行下:
hello [Finished in 0.1s]
好记性不如烂笔头,写一下理解一下会好很多。
下面还分享类的装饰器,以及装饰器所用场景。