python装饰器2 装饰器的介绍

1. 引子

1.1 实现函数执行时间的计算

1)被装饰函数不带参数的情况

a)执行timmer(func) 实现

代码如下

import time

def timmer(funcname):
    start = time.time()
    funcname()
    print(time.time()-start)

def func1():
    num = 0
    for i in range(100000):
        num += i

def func2():
    num = 0
    for i in range(10000000):
        num +=1

timmer(func1)	# 0.007220029830932617
timmer(func2)	# 0.6535038948059082

上述代码,分别将func1的函数名作为参数传给timmer函数,而timmer函数在执行传入的func1函数前会先对执行func1的时间点做记录,待函数执行完之后,再由当前时间减去执行开始时的时间,计算得出函数func1执行耗费的时间(func2同理)

如果在现实情况中,func1函数可能会被调用1000+次,那么我就需要写1000+次的timmer(func1)来调用func1函数;甚是麻烦
那么我是否能够在不改变func1的调用方式的前提下,同时添加上函数执行时间计算的功能呢?

b)不改变被装饰函数的调用方式下实现

**关键点:**1.修饰器返回inner函数;2.func=timmer(func)

代码如下

import time

def timmer(funcname):
	def inner():
		start = time.time()
		funcname()
		print(time.time() - start)
	return inner

def func1():
	sum_num = 0
	for i in range(100000):
		sum_num += i
	print('sum_num:',sum_num)

func1 = timmer(func1)
func1()

'''
输出:
sum_num: 4999950000
0.00814199447631836
'''

从上面代码可以看到,通过func1()的方法调用了func1函数,函数调用方式未发生改变,但是却实现了计算执行该函数所耗费的时间的功能

代码执行逻辑:
在这里插入图片描述
从上述执行逻辑中我们可以知道:
1)第3步,将上述定义的func1函数名作为参数传入了timmer函数,此时funcname即func1;而inner函数里的funcname()即func1()
2)从第6步开始,func1函数就已经重新赋值为inner函数,执行func1即执行timmer内部的inner函数,而第3步已经将func1函数传入到了inner函数中,那么我们又可以根据inner函数的具体内容可以知道,在执行原func1函数前记录时间点,执行之后计算所用时间

上述func1函数不带参数,那么如果func1带参数带化该怎么办呢?

2)被装饰函数带参数的情况

关键点: inner函数接收参数n,同时传入内部的funcname函数,funcname函数也接受参数n

代码如下:

import time

def timmer(funcname):
	def inner(n):		# 此处接收参数
		start = time.time()
		funcname(n)		# 同时此处也要接收参数
		print(time.time() - start)
	return inner

def func1(n):
	sum_num = 0
	for i in range(n):
		sum_num += i
	print('sum_num:',sum_num)

func1 = timmer(func1)
func1(100000)

'''
输出:
sum_num: 4999950000
0.00752711296081543
'''

代码执行逻辑:
在这里插入图片描述
从上述描述我们得出:
关键在于,最后执行func1即执行inner,参数最终需要传递给funcname函数,就需要先传递给inner函数,然后再有funcname函数接收

接下来又有这样一个问题,现在有一个func2函数,不需要传参数,但是也想计算函数执行时间,现在该如何?

一个函数带参数,一个函数不带参数,均要计算函数执行时间,该如何?

3)传参数量不同的函数被修饰的情况

**关键点:**应用*args**kwargs

代码1如下:

import time

def timmer(funcname):
	def inner(n):		# 此处接收参数
		start = time.time()
		funcname(n)		# 同时此处也要接收参数
		print(time.time() - start)
	return inner

def func1(n):
	sum_num = 0
	for i in range(n):
		sum_num += i
	print('sum_num_f1:',sum_num)

def func2():
    sum_num = 0
    for i in range(1000000):
        sum_num += i
    print('sum_num_f2',sum_num)

上述代码,timmer函数里的inner函数是带参数的,而func2不带参数,那么如果直接func2 = timmer(func2) 肯定会报错,该如何是好?
这个时候就需要应用我们的*args**kwargs 传位置参数与关键字参数的知识了

修改后的代码2如下:

import time

def timmer(funcname):
	def inner(*args,**kwargs):
		start = time.time()
		funcname(*args,**kwargs)
		print(time.time() - start)
	return inner

def func1(n):
	sum_num = 0
	for i in range(n):
		sum_num += i
	print('sum_num_f1:',sum_num)

def func2():
    sum_num = 0
    for i in range(1000000):
        sum_num += i
    print('sum_num_f2',sum_num)

func1 = timmer(func1)
func2 = timmer(func2)

func1(100000)
func2()

'''
输出:
sum_num_f1: 4999950000
0.007891178131103516
sum_num_f2 499999500000
0.06601905822753906
'''

此时,无论要计算执行时间的函数不带参数、带位置参数、关键字参数,均可通过func=timmer(func)的方法来将func传入timmer,进而进行执行时间的计算

我们说过,要尽量保持函数执行方式不变的情况下来计算其执行时间,但是上述所有代码中,实际上在调用函数之前都是执行了func = timmer(func)的;如何在不执行该语句的情况下,直接执行函数而达到计算函数时间执行时间的功能呢?

1.2 @timmer 代替 func=timmer(func)

关键点:func=timmer(func)可以不写在调用函数的位置,可以提前直接写在定义函数的位置,语法是:@timmer

代码如下:

import time

def timmer(funcname):
	def inner(*args,**kwargs):
		start = time.time()
		funcname(*args,**kwargs)
		print(time.time() - start)
	return inner

@timmer     # 相当于 func1 = timmer(func1)
def func1(n):
	sum_num = 0
	for i in range(n):
		sum_num += i
	print('sum_num_f1:',sum_num)

@timmer     # 相当于 func2 = timmer(func2)
def func2():
    sum_num = 0
    for i in range(1000000):
        sum_num += i
    print('sum_num_f2',sum_num)


func1(100000)
func2()

'''
输出:
sum_num_f1: 4999950000
0.006793975830078125
sum_num_f2 499999500000
0.06452298164367676
'''

目前即可直接在执行func1或者func2函数时加载上计算时间的功能了。

目前还有个小问题需要调整一下。上述代码中func1和func2函数均用的print函数来查看结果,但在实际情况中通常用return来返回函数执行的结果。稍作修改即可

完善代码,通过return返回结果:

import time
def timmer(funcname):
    def inner(*args,**kwargs):
        start = time.time()
        ret = funcname(*args,**kwargs)		# ret接收funcname的返回结果
        print(time.time() - start)
        return ret		# 返回ret
    return inner

@timmer     # 相当于 func1 = timmer(func1)
def func1(n):
    sum_num = 0
    for i in range(n):
        sum_num += i
    return sum_num		# return返回结果

@timmer     # 相当于 func2 = timmer(func2)
def func2():
    sum_num = 0
    for i in range(1000000):
        sum_num += i
    return sum_num		# return返回结果

ret1 = func1(100000)
ret2 = func2()

print('ret1:',ret1)
print('ret2:',ret2)
'''
输出:
0.0065839290618896484
0.06833600997924805
ret1: 4999950000
ret2: 499999500000
'''

2. 什么情况下用装饰器

1)在已经写好发版的程序的基础上,需要对一个函数执行前后增加功能的时候
  开放封闭原则:
    开放:对扩展是开放的
    封闭:对修改是封闭的
  即保证了程序/函数的可扩展性,由保证了已写好的函数不被任意修改
2)有的时候也会写好一些装饰器,加载需要装饰的函数上面
  如:对每一个动作(函数)加载一个日志记录的装饰器,这样就能实时记录用户操作日志

3. 装饰器的固定格式

3.1 固定格式

# 定义装饰器
def 装饰器名(func):
	def inner(*args,**kwargs):
		'''执行被装饰的函数之前要做的事'''
		ret = func(*args,**kwargs)
		'''执行被装饰的函数之后要做的事'''
		return ret
	return inner

# 用装饰器装饰test函数
@装饰器名
def test():
	pass

# 执行test函数
test()

3.2 示例

import time
def wrapper(func):
	def inner(*args,**kwargs):
		ret = func(*args,**kwargs)
		t = time.strftime('%Y-%m-%d %H:%M:%S')
		print('%s时刻[%s]执行了%s函数' % (t,args[0],func.__name__))
		return ret
	return inner

@wrapper
def login(name):
	print('%s登录了...' % name)

login('tom')

'''
输出:
tom登录了...
2019-11-18 00:26:00时刻[tom]执行了login函数
'''

如上述,就可以在执行login函数之后,输出具体的执行日志;如果进一步将日志写入日志文件,即可实现最基础的日志记录功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值