Python函数修饰符@的作用是为现有函数增加额外的功能.此文章来谈一谈它在日志记录中起到的很好的作用.
举个例子:
import datetime
__DEBUG__ = Ture
def log(func):
if __DEBUG__:
print('函数开始于',datetime.datetime.now())
func()
if __DEBUG__:
print('函数结束于',datetime.datetime.now())
def test():
print('test')
test_lst[]
for i in range(100):
test_lst.append(i)
log(test)
log是一个日志函数,当程序运行于DEBUG模式下时,用于记录函数运行的开始时间,和结束时间。
/usr/bin/python3.6 /home/tarena/pk/1906/test.py
函数开始于 2019-09-18 19:11:14.802328
test
函数结束于 2019-09-18 19:11:14.802397
可是这段代码有个弊端,在测试某个参数时,我们需要,添加类似于log(test)这样的代码,不需要后把它删除;这样做很麻烦。这时我们就需要@函数修饰符。修改下上面的代码,删除 log(test)这行代码.在test函数的上方加入 @log 装饰器
@log
def test():
pass
这段代码和上述代码功能是一样的。
可是问题又来了;运行上述代码,发现我们没有调用 test 函数,test就已经被执行了。因为 @log 等于 log(test)。修改代码:
import datetime
__DEBUG__ = True
def log(func):
def wrapper():
if __DEBUG__:
print('函数开始于',datetime.datetime.now())
func()
if __DEBUG__:
print('函数结束于',datetime.datetime.now())
return wrapper
@log
def test():
print('test')
test_lst = []
for i in range(100):
test_lst.append(i)
我们在log函数中定义了一个wrapper函数,并将这个函数返回
相当于 log(test) ,而调用log后,test函数不会被立即执行,而是把test进行再次包装,返回这个再包装的函数地址。
需要这样才能让函数运行
log(test)()#未使用@log装饰器的调用方法
在使用@log装饰器后,直接调用test()即可自动获取函数运行日志,我们可以为很多需要测试的函数添加@log装饰器,不需要直接将__DEBUG__设置成 False
在使用装饰器后,test函数变成了wrapper函数
/usr/bin/python3.6 /home/tarena/pk/1906/test.py
<function log.<locals>.wrapper at 0x7f244ff869d8>
上为 print(test)的运行效果。
它的执行顺序是这样的。
运行 test(使用了@log装饰器)函数后,首先被执行的是log函数,log函数定义一个wrapper函数,并返还给test(使用了@log装饰器),此时test函数就是wrapper函数。
替换过程完成。
此时我们再调用test函数(已经被替换成wrapper),wrapper函数打印运行记录,并执行test函数(这里的test函数是最原始的test)
如何给函数添加参数
有这样的情节
先修改上文代码test()函数
def test(a,b):
print('test')
test_lst = []
for i in range(a):
test_lst.append(b)
test(1,2)
这种情况我们应该怎么修改wrapper呢
其实我们了解整个执行流程后,就已经很清楚了,只需这样修改
def wrapper(*args,**kwargs):
if __DEBUG__:
print('函数开始于', datetime.datetime.now())
func(*args,**kwargs)
if __DEBUG__:
print('函数结束于', datetime.datetime.now())
*args是将参数以元组的形式打包成tuple给函数体调用
**kwargs是将关键字参数打包成dict给函数体调用
这里不对这两种参数打包形式进行细说。
为@log装饰器添加参数
有这种应用场景
fp = open('out.log','a')
@log(level=1,file=fp)
@log(1,fp)
怎么能让装饰器拥有参数呢?
很简单,只需要再次包装
import datetime
import sys
__DEBUG__ = True
fp = open('out.log','a')
def log(level=1,file=sys.stdout):
def _log(func):
def wrapper(*args,**kwargs):
if __DEBUG__:
print('函数开始于', datetime.datetime.now())
func(*args,**kwargs)
if __DEBUG__:
print('函数结束于', datetime.datetime.now())
return wrapper
return _log
@log(file=fp)
def test(a,b):
pass
其中原理和前面提到的test函数被替换的概念是一样的。
为@log装饰器添加方法
又有这样的场景
@log.sendto('127.0.0.1:9009'.timeout=30)
我现在希望添加一个方法,让其功能更加丰富。
那么log函数有点顶不住了,虽有方法在log函数中实现这一目的,但是有一种更简单的方法,那就是使用类:
在这里的类中实现了 call 方法,实例对象也将成为一个可调用对象,
class _log:
def __call__(self, level=1,file=sys.stdout):
def _log_wrapper(func):
def wrapper(*args,**kwargs):
if __DEBUG__:
print('函数开始于', datetime.datetime.now())
func(*args,**kwargs)
if __DEBUG__:
print('函数结束于', datetime.datetime.now())
return wrapper
return _log_wrapper
log = _log()#使用前不要忘记实例化
这就实现了前面的方法
现在只需要在 _log类中定义方法,即可像@log装饰器添加方法
def sendto(self,addr,timeout=20):
pass
具体的sendto功能就不写了。
以此类推,我们就可以自己设计一些多样的功能作为模板打包,以便于改善自己的代码。
完整源码
import datetime
import sys
__DEBUG__ = True
class _log:
def __call__(self, level=1,file=sys.stdout):
def _log_wrapper(func):
def wrapper(*args,**kwargs):
if __DEBUG__:
print('函数开始于', datetime.datetime.now())
func(*args,**kwargs)
if __DEBUG__:
print('函数结束于', datetime.datetime.now())
return wrapper
return _log_wrapper
def sendto(self,addr,timeout=20):
pass
log = _log()#使用前不要忘记实例化
fp = open('out.log','a')
# @log(level=1,file=fp)
# @log(1,fp)
@log(file=fp)
def test(a,b):
pass
print('test')
test_lst = []
for i in range(a):
test_lst.append(i*b)
test(1,2)