Decorators
本节目标:掌握python的重要概念装饰器。
本节内容:装饰器的理解以及实践,用装饰器实现奥特曼打怪兽
本节技术点:wrap、Decorator、传参
本节阅读需要(20)min。
本节实操需要(20)min。
下面我们将用鲜活的实例去讲解装饰器!!!
前言
从目标上来看,装饰器最基础的就是丰富了函数的功能,相当于锦上添花。又相当于触发器,当调用指定函数的时候
会发生一些行为,比如检查,计数,日志等等反应式的行为。
从形式上来讲装饰器放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为 装饰器
从本质上看,装饰器是函数,他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。
同时也加强了复用性。
一、预备知识
基础结构
上文说了像帽子一样,基本框架如下:
def decorator(func): # 装饰器定义,相当于入口
def wrapper(*args, **kw): # 起实际装饰功能的函数
return func() # 被装饰的函数,这里会添加功能等
return wrapper
@decorator
def function():
print("hello, decorator")
function()
这一段装饰器其实什么都没做。但是极为重要!!!
函数的本质
我相信没有人不知道冯诺依曼的计算机结构模型吧!
里面有这样一句,指令和信息都作为内容存储。
所以函数名的本质是指针
def hi(name="乔峰"):
return "hi " + name
print(hi())
# output: 'hi 乔峰'
# 我们甚至可以将一个函数赋值给一个变量,比如
greet = hi
# 我们这里没有在使用小括号,因为我们并不是在调用hi函数
# 而是在将它放在greet变量里头。我们尝试运行下这个
print(greet())
# output: 'hi 乔峰'
# 如果我们删掉旧的hi函数,看看会发生什么!
del hi
print(hi())
#outputs: NameError
print(greet())
#outputs: 'hi 乔峰'
为啥greet还是成功的?
因为del删除的只是hi指向函数实际内存位置的指针。函数的实际存储并没有删除,因为greet指向它的指针没有消失,所以这一段函数的实际内存不会被垃圾收集机制处理掉。
自然greet还是有效的。
所以函数内部定义函数是完全可以的。但是生存周期的问题,内部定义的函数在外部无法使用。
def hi(name="yasoob"):
print("now you are inside the hi() function")
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
print(greet()) # greet定义在hi内部,可以使用
print(welcome())
print("now you are back in the hi() function")
hi()
greet() # greet定义在hi内部,所以在外部无法使用
结合上面的基础结构里面的wrapper,也是定义在内部的。所以外部不可见,所以去什么名字无所谓,只要名字在内部不冲突就行,所以一般都叫wrapper,初学者很容易被搞糊涂,相当于一次性手套,用完一个就丢弃,虽然看似一样,但是已经不同了。
有点指针嵌套的意思。
二、装饰器的结构
基础装饰器
装饰装饰,我们也可以认为是加戏。。。
所以装饰器也可以认为是个戏精
def aoteman(func):
print("怪兽起床了,起床气很重") # 区域1,只要被装饰的函数调用了就立即执行。怪兽出现了,必然是被吵醒的
def roubo():
print("奥特曼和怪兽愉快的肉搏")
def siteleimuguangxian():
print("斯特雷姆光线,怪兽卒")
def caicaicai():
print("怪兽要踩踩踩")
def wrapTheFunction(): # 返回的是wrapTheFunction,所以这个才是装饰器功能函数wrapper
print("我很生气,后果很严重") # 区域2 在具体的func执行之前执行,怪兽生气了,装饰了怪兽想要破坏的原因
func() # 被装饰的函数,怪兽想要破坏
caicaicai() # 区域3,执行完被装饰的函数func执行的功能,装饰了怪兽破坏念头的结果
roubo()
siteleimuguangxian()
print("") # 区域4,但是效果实际上和区域1一致,因为wrapTheFunction在最后调用,1和4都在他前面,所以一致
return wrapTheFunction
@aoteman
def monster():
print("我要消灭。。。就东京吧")
monster()
被装饰的函数monster,实际上是改变了的相当于实际的wrapTheFunction。
aoteman这个形式上的装饰器实际上的功能是:
- 为wrapper提供入口。
- 为wrapper提供生存周期。
所以装饰器的功能核心是wrapper,不是Decorator函数也不是被装饰的函数。
原函数的参数也是传给的wrapper。
带参数的装饰器
上述的实例有没有觉得就像是两个人偶在程序化的打架。
故事的主角都没有,是哪一个奥特曼打哪一个怪兽呢。。。
我们上面说到了,装饰器函数相当于是入口。但是这个时候我们发现原来的框架已经传入了func。返回wrapper。
没有更多的位置来容纳其余的参数了。
怎么解决呢?
这个时候我们的思路是类似的。我们因为需要给装饰器函数和我们需要传入的参数一个相同的生存空间(不然变量无法使用)
所以方法也就有了,在外层嵌套一层函数传入我们需要的参数,然后返回我们本来的装饰器函数,问题就解决的!!!
带参数的装饰器框架:
def last_decorator(name):
def first_decorator(func): # 装饰器定义,相当于入口
def wrapper(*args, **kw): # 起实际装饰功能的函数
print(name)
return func() # 被装饰的函数,这里会添加功能等
return wrapper
return first_decorator
@decorator
def function():
print("hello, decorator")
first_decorator,wrapper 叫什么无所谓。。。因为都是默默付出并且功成身退的。
类装饰器
tips:python中函数和类的底层机制其实十分相似。。。抽象点,甚至可以说差不多一样。。。
import time
class Decorator:
def __init__(self, func): # 固定格式
self.func = func
def defer_time(self):
time.sleep(5)
print("延时结束了")
def __call__(self, *args, **kwargs): # 固定格式
self.defer_time()
self.func()
@Decorator
def f1():
print("延时之后我才开始执行")
f1()
我们发现基本上只是格式变化了一下。。。
不过Decorator最好首字母大写,这样可以判定是类装饰器。
init,call,必须要写!!!格式相当于函数装饰器中的wrapper。但是注意init和call可不能乱叫。。。
带参数的类装饰器
带参数的类装饰器反而比带参数的函数装饰器好些,也容易理解的多。
import time
class Decorator:
def __init__(self, func):
self.func = func
def defer_time(self,time_sec):
time.sleep(time_sec)
print(f"{time_sec}s延时结束了")
def __call__(self, time): # 直接传入参数就行
self.defer_time(time)
self.func()
@Decorator
def f1():
print("延时之后我才开始执行")
f1(5)
三、装饰器实战–常用的装饰器
日志打印器
def logger(func):
def wrapper(*args, **kw):
print('开始执行:{} 函数了:'.format(func.__name__))
# 真正执行的是这行。
func(*args, **kw)
print('well done')
return wrapper
@logger
def add(x, y):
print('{} + {} = {}'.format(x, y, x+y))
add(100, 66)
时间装饰器
def timer(func):
import time
def wrapper(*args, **kw):
t1=time.time()
# 这是函数真正执行的地方
func(*args, **kw)
t2=time.time()
# 计算下时长
cost_time = t2-t1
print("花费时间:{}秒".format(cost_time))
return wrapper
总结
画虎画皮难画骨。
对于装饰器,我们不能只看被装饰函数的功能,一般而言装饰器起到了检验限制或者丰富扩展的功能。
需要仔细核对。
装饰器的本质在于命名空间和生存周期的构建,而方法在python中就是函数。(在cpp中大概就是域了)
最内层的规律是死的,也就是wrapper基本是固定的,其他的不论多少层的嵌套,只不过是由低到高不断的加戏而已!!!
装饰器只有最外层的函数名称重要,毕竟要到处贴这个装饰。里面的直接模板就OK,保证嵌套关系。。。
函数装饰器只有最外层米名称重要,内部对应就好(不然递归会失败)。
类装饰器就更加的固定了,内部格式固定。
装饰器也是复用的一种体现。最常见的其实是作为一个开关或者记录器去通用的监督其他的函数。
大概可以认为是函数里面当官的吧。。。