Python修饰器的使用

Python基础知识 专栏收录该内容
1 篇文章 0 订阅

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)
  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值