一、文档字符串
无参装饰器和带参装饰器有什么区别呢?我们先来看文档字符串
文档字符串是什么东西呢?
文档字符串.
●Python文档字符串Documentation Strings
●在函数(类、模块)语句块的第一行,且习惯是多行的文本,所以多使用三引号
●文档字符串也算是合法的一条语句
●惯例是首字母大写,第一行写概述,空一行,第三行写详细描述
●可以使用特殊属性__doc__ 访问这个文档
文档所在的对象上有一个特殊的属性叫做__name__,特殊属性就带下划线,还有特殊属性doc。自己也可以创建
它的用法如下
def add(x,y):
"""add function
x int
y int
return int
"""
通过文档字符串说明了add函数的参数是什么类型,返回值是什么类型
print(add.__name__,add.__doc__)
通过调用add属性__doc__可以看到三引号里面文档
使用print(help(add)) 也可以输出文档
我们来写一个装饰器用来,记录函数执行时间的
import datetime
import time
def logger(fn):
def wrapper(*args, **kwargs):
''' wrapper function+++++++'''
start=datetime.datetime.now()
ret = fn(*args, **kwargs)
delta=(datetime.datetime.now()-start).total_seconds()
print("Function {} took {:.2f}s".format(fn.__name__,delta))
return ret
return wrapper #delta 输出多少秒
@logger # add = logger(add)
def add(x, y):
'''add function~~~~~~ '''
time.sleep(2)
return x + y
我们对里的装饰器加字符串文档
print(add.__name__,add.__doc__)
这里打印会发现输出了:
wrapper wrapper function+++++++
发现并没有输出add函数中的字符串文档,有点露馅了,我们这种装饰,只是
伪装的像add,名字是一样了。但是我们知道add = logger(add)
add=wrapper add函数指向了wrpper函数的地址空间了。wrpper标识符虽然
已经消亡了,但是它所指向的空间,被add标识符记住了。
现在问题是,我仅仅是标识符伪装了,都是add,但是内部还没有进行处理?该怎么处
理就是你现在能不能把这个warpper伪装了像真正的add函数一样呢?
名字和文档字符串都应该显示的是add函数的内容
简单的总结一下我们的问题是什么
print(add.__name__,add.__doc__)
输出了
wrapper wrapper function
只是标识符用了add,文档字符串和名字依然用了wrapper,但是我需要
输出add函数中的文挡字符串信息?该怎么办呢?
要达到的效果是 输出 add add function~~~~~~
怎么做有思路吗?
问题出在,因为我返回的是wrapper函数,wrapper所指向的函数对象呢,里面的函
数名已经定义为wrapper了,里面的文档也写死了是
''' wrapper function ++++'''我们把doc文档换掉就好了,但是在哪里换呢?
wrapper.__name__=fn.__name__
wrapper.__doc__=fn.__doc__
在wrapper函数中加这两行代码
封装成函数
def copy_properties(dst,src):
print(dst.__name__,'***',src.__doc__)
dst.__name__=src.__name__
dst.__doc__=src.__doc__
#其他属性省略
print(dst.__name__, '***', src.__doc__)
二、带参数装饰器
现在的问题是,这个用法比较麻烦能不能改造成装饰器?我们来看代码
def logger(fn):
@copy_properties(fn)
def wrapper(*args, **kwargs):
''' wrapper function+++++++'''
print('调用前增强功能')
start=datetime.datetime.now()
ret = fn(*args, **kwargs)
print('调用后增强功能')
delta=(datetime.datetime.now()-start).total_seconds()
print("Function {} took {:.2f}s".format(fn.__name__,delta))
#delta 输出多少秒
return ret
copy_properties(wrapper,fn)
return wrapper
现在加入了@copy_properties(fn),这个装饰器就是带参数装饰器。
因为这个装饰器参数,需要调用一个函数参数,所以是带参装饰器
我们在来看整体代码
import datetime
import time
#变成装饰器
def copy_properties(src):
def copy_pro(dst):
dst.__name__=src.__name__
dst.__doc__=src.__doc__
return copy_pro
def logger(fn):
@copy_properties(fn) #装饰了fn函数
def wrapper(*args, **kwargs):
''' wrapper function+++++++'''
start=datetime.datetime.now()
ret = fn(*args, **kwargs)
delta=(datetime.datetime.now()-start).total_seconds()
print("Function {} took {:.2f}s".format(fn.__name__,delta))
#delta 输出多少秒
return ret
#copy_properties(wrapper)(fn)#copy_pro(fn)
return wrapper
@logger # add = logger(add)
def add(x, y):
'''add function~~~~~~ '''
time.sleep(2)
return x + y
# add = logger(add) 这行代码不需要了注释掉
print(add.__name__,add.__doc__)
上面代码执行会报错的我们来分析下错误原因:
这段代码的执行顺序如下:
1.首先执行到@logger的时候,它其实是执行了add=logger(add)这个语句,
因为@+函数标识符是装饰器语法,装饰器他会把它下面最近的函数标识符这里是
add给抽取上来作为参数。
因为add=logger(add)本质上是函数调用了,根据优先级原则,先做等式右边的
logger(add)
2.进入到logger 之后发现第一行是@copy_properties(fn)发现也是一个装饰
器我们会发现这个@copy_properties(fn)装饰器后面是有参数的,这个参数是
fn
根据第一步我们知道,参数传入进来的其实是add函数。
在整个logger函数的执行,会把它下面定义的函数wrapper定义好,在进行
@copy_properties这个执行,它实质上也是一次函数调用,等价于
wrapper=copy_propertties(fn)(wrapper),这是一个有参数的装饰器
修饰了wrapper.
3.第三步我们需要进入到copy_propertties(src)这个函数内部进行执行,传
进去参数是add函数,这个函数执行完会返回一个函数copy_pro(),我们在把
参数wrapper给传入进去,这样一来dst和src都知道是谁了,目的是wrapper
源是src,add.
但是copy_pro()函数内部只有两个语句,dst.__name__=src.__name__
dst.__doc__=src.__doc__,我们又知道,函数内部不写返回值,默认就是
返回NULL/相当于执行了一个return None。
在根据装饰器语法@copy_properties(fn),执行这个相当于执行了
wrapper=copy_propertties(fn)(wrapper),那我们就知道了,现在
wrapper的值是NULL了,所以后面主函数我们在执行
print(add.__name__,add.__doc__)会直接报错的
add=logger(add)-->add=wrapper--->退出add=None
我们怎么修改?让代码变的正确,我们写装饰器的时候,要明确给返回值。
因为修改的是目的函数,所以我们就要返回dst这个参数函数就看可以了
def copy_properties(src):
def copy_pro(dst):
dst.__name__=src.__name__
dst.__doc__=src.__doc__
return dst
return copy_pro
dst就是wrapper,修改一下返回。返回包装函数本身
上面这种就是带参装饰器.