Python函数修饰符@的作用是为现有的函数增加额外的功能;其作用非常强大,今天我们就来谈谈它是如何在日志记录中起到很好的作用的。 先看一个例子:
import datetime
__DEBUG__ = True
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() 是一个日志函数,用于记录函数运行的开始时间,和结束时间。 可是这段代码有个弊端,在测试某个函数时,我们需要添加类似于log(test)这样的代码,不需要后把它删除;这样做很麻烦。 还好我们有@函数装饰符。修改前文中的代码,移除 log(text). 在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()即可获取函数运行日志,我们可以为很多需要测试的函数添加@log装饰器,不需要用这个功能直接将__DEBUG__设置成 False。 它的执行顺序是这样的:在为函数添加了@log装饰器后,log函数就被执行,而使用了@log装饰器的 test 函数被作为log函数的传入值使用,在log函数中,对test函数进行了一个简单的包装,并将包装好的函数返回,也就是log函数中的wrapper函数;此时test 函数实际变成了 wrapper函数。
如何给函数添加参数
前文中的 test 函数是没有使用参数的,如果你要定义和使用参数,应该这样修改:
def test(a,b):
print('test')
test_lst = []
for i in range(a):
test_lst.append(i*b)
test(1,2)
包装函数:
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 sys
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(),file=file,flush=True)
func(*args,**kwargs)
if __DEBUG__:
print('函数结束于',datetime.datetime.now(),file=file,flush=True)
return wrapper
return _log
@log(file=fp)
现在你可以指定日志的输出文件了 为@log装饰器添加方法 又有这样的场景: @log.sendto(‘127.0.0.1:9009’.timeout=30) 你可以将 .sendto 方法理解为将日志发送到指定服务器; 那么log函数就开始有点虚脱了,虽然有方法在log 函数中实现这一目的,但是比较复杂,有一种更简单的方法,就是将log函数转换成类:
class _log:
def __call__(self,level=1,file=sys.stdout):
def _log_wrapper(func):
def wrapper(*args,**kwargs):
if __DEBUG__:
print('函数开始于',datetime.datetime.now(),file=file,flush=True)
func(*args,**kwargs)
if __DEBUG__:
print('函数结束于',datetime.datetime.now(),file=file,flush=True)
return wrapper
return _log_wrapper
使用前别忘记实例化,log=_log() 现在只需要在 _log类中定义方法,即可向@log装饰器添加方法: def sendto(self,addr,timeout=20): pass 完整源码:
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(),file=file,flush=True)
func(*args,**kwargs)
if __DEBUG__:
print('函数结束于',datetime.datetime.now(),file=file,flush=True)
return wrapper
return _log_wrapper
def sendto(self,addr,timeout=20):
pass
log=_log()
fp = open('out.log','a')
@log(file=fp)
def test(a,b):
print('test')
test_lst = []
for i in range(a):
test_lst.append(i*b)
test(1,2)
|