装饰器

装饰器

使用目的:在不修改函数源代码的基础上,添加额外的功能,类似打补丁

了解装饰器之前先要清除闭包的概念

闭包

  1. 引用了外部自由变量的函数
  2. 自由变量:不在当前函数定义的变量
  3. 特性:自由变量会和闭包函数同时存在
  4. 即使程序离开发布作用域,如果闭包仍然可见,绑定变量不会销毁
  5. 每次运行外部函数都会重新创建闭包
def startAt(x):
    def incrementBy(y):	# 闭包函数
        return x+y	# x相对于incrementBy就是外部自由变量
  	return incrementBy	# 返回incrementBy闭包函数的地址

a = startAt(1)	# 此时a拿到的是incrementBy的地址,只是一个函数名
a(2)	# 实质是调用了incrementBy(2),返回的是x+2,而x在上一句已经赋值为1,所以结果是1+2=3

而装饰器就是把x本来是传值的,改成传函数名,即函数地址

此处为了更好理解一些,把x改成func

import time 

def startAt(func):	# 接收一个函数地址
    def incrementBy(*args,**kwargs):  # 设定接收被传进来的func的所有参数
        beg = time.time()
        res = func(*args,**kwargs)   # 即在incrementBy函数中调用mysleep()
        print(f'user time:{time.time()-beg}')
        return res
    return incrementBy # 返回incrementBy函数地址

@startAt
def mysleep():
    time.sleep(1)
    
# a = startAt(mysleep)
# a()  实际newsleep()就是调用的incrementBy()

疑问:明明startAt只是拿到了函数的地址,为什么可以把参数传到incrementBy中?

可以这么理解,函数的形参和函数名是打包在一起的,统一记录在同一个地址中,函数在调用时才会使用形参,所以incrementBy并不知道穿过来的是什么形参,它只是贪婪的全部接收,让func在调用时直接使用就行了

import time

# 使用类装饰器适合给装饰器加参数
# 使用类作为装饰器
class LogTime:
    def __init__(self,user_int=False)
    	self.user_int = user_int
    
	def __call__(self,func):	# 类方法
		def _log(*args,**kwargs):
			beg = time.time()
			res = func(*args,**kwargs)
            if self.user_int:
                print(f'user time:int({time.time()-beg})')
            else:
				print(f'user time:{time.time()-beg}')
        return res
    return _log

@LogTime(user_int=False)	# 使用括号是因为类需要生成一个实例
def mysleep():
    time.sleep(1)
    
# a = LogTime(user_int=False)
# a()

其实不使用类装饰器也能在装饰器中加参数,但是需要在外层再封装一个工厂函数用来接收实参
比如:

def get_parameter(*args,**kwargs):  # 工厂函数,用来接受@get_parameter('index.html/')的'index.html/'
    def log_time(func):
        def make_decorater():
            print(args,kwargs)
            print('现在开始装饰')
            func()
            print('现在结束装饰')
        return make_decorater
    return log_time
 
@get_parameter('index.html/')
def test():
    print('我是被装饰的函数')
    # return num+1
 
test()  # test()=make_decorater()

优点和缺点

  1. 优点:在不修改源代码的同时增加额外的功能
  2. 缺点:原函数的名字会被修改
    1. 避免原函数的名字修改:在内嵌函数上使用@functools.wrap(fn)装饰

测量函数执行时间装饰器

import time

def cal_time(func):
    def wrapper(*args,**kwargs):
        time1 = time.time()
        result = func(*args,**kwargs)
        time2 = time.time
        print("%s running time: %s" %(func.__name__,time2-time1))
        return result
    return wrapper
posted @ 2021-09-09 07:32  注入灵魂  阅读(69)  评论(0)    收藏  举报