Python的装饰器概念

为什么需要装饰器

我们假设你的程序实现了say_hello()和say_goodbye()两个函数。

def say_hello():
	print("hello!")
    
def say_goodbye():
	print("hello!")  # bug here

if __name__ == '__main__':
	say_hello()
	say_goodbye()

但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:

[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!

好,小A是个毕业生,他是这样实现的。

def say_hello():
    print("[DEBUG]: enter say_hello()")
    print("hello!")

def say_goodbye():
    print("[DEBUG]: enter say_goodbye()")
    print("hello!")

if __name__ == '__main__':
    say_hello()
    say_goodbye()

很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。

def debug():
    import inspect
    caller_name = inspect.stack()[1][3]
    print("[DEBUG]: enter {}()".format(caller_name))

def say_hello():
    debug()
    print("hello!")

def say_goodbye():
    debug()
    print("goodbye!")

if __name__ == '__main__':
    say_hello()
    say_goodbye()

是不是好一点?那当然,但是每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?

那么装饰器这时候应该登场了。

装饰器的定义

要想了解装饰器,首先要了解一个概念,闭包。什么是闭包,一句话说就是,在函数中再嵌套一个函数,并且引用外部函数的变量,这就是一个闭包了。

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。

装饰器的使用

怎么写一个装饰器
在早些时候 ,为一个函数添加额外功能的写法是这样的。

def debug(func):
    def wrapper():
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func()
    return wrapper

def say_hello():
    print("hello!")

say_hello = debug(say_hello)  # 添加功能并保持原函数名不变
print(say_hello())

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

def debug(func):
    def wrapper():
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func()
    return wrapper

@debug
def say_hello():
    print("hello!")

print(say_hello())

这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper接受和原函数一样的参数,比如:

def debug(func):
    def wrapper(something):  # 指定一毛一样的参数
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func(something)
    return wrapper  # 返回包装过函数

@debug
def say(something):
    print("hello {}!".format(something))

print(say('word'))

这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。

#声明装饰器
def debug(func):
    def wrapper(*args, **kwargs):  # 指定宇宙无敌参数
        print("[DEBUG]: enter {}()".format(func.__name__))
        print('Prepare and say...',)
        return func(*args, **kwargs)
    return wrapper  # 返回
#声明函数和引用装饰器
@debug
def say(something):
    print("hello {}!".format(something))
#调用函数,调用装饰器
print(say('word'))

至此,你已了解到了装饰器写法。

装饰器调用顺序

装饰器是可以叠加使用的,那么使用装饰器以后代码是啥顺序呢?
对于Python中的”@”语法糖,装饰器的调用顺序与使用 @ 语法糖声明的顺序相反。

def tips(func):
	def nei(a,b):
		print('start')
		func(a,b)
		print('stop')
	return nei
	
@tips
def add(a,b):
	print(a+b)
if __name__ == '__main__':
	add(4,5)

在上个例子中,”add(4,5)=tips(add(4,5))”。

扩展0:常规的函数写法

import time
def i_can_sleep():
	time.sleep(3)
start_time = time.time()
i_can_sleep()
stop_time = time.time()
print("函数运行了 %s 秒" %(stop_time - start_time))

正常写法首先是由start_time调用时间函数,显示开始时间,之后调用写好的函数i_can_sleep睡眠3秒钟结束,再调用时间函数stop_time表示结束时间,最后用结束时长减去开始时长,得到的时间,就可计算一个函数的运行时间,一共用了多少时长。

扩展1:装饰器函数语法

import time
def timeer(func):
	def wrapper():
		start_time = time.time()
		func()
		stop_time = time.time()
		print("函数运行了 %s 秒" %(stop_time - start_time))
	return wrapper
#上面实例是闭包的写法,装饰器函数

@timeer   #调用装饰器
def i_can_sleep():
	time.sleep(3)

#调用i_can_sleep()函数时,前有装饰器,需要调用装饰器,也就是timeer(func)函数
i_can_sleep()

扩展2:带参数且参数不在装饰器里的函数语法

def tips(func):
	def nei(a,b):
		print('start')
		func(a,b)
		print('stop')
	return nei
	
@tips
def add(a,b):
	print(a+b)

@tips
def sub(a,b):
	print(a-b)

print(add(4,5))
print(sub(10,5))

扩展3:带参数且参数在装饰器里的函数语法

def new_tips(argv):
	def tips(func):
		def nei(a,b):
			print('start %s' %argv,func.__name__)
			func(a,b)
			print('stop')
		return nei
	return tips
	
@new_tips('add')
def add(a,b):
	print(a+b)

if __name__ == '__main__':
	add(4,5)
发布了26 篇原创文章 · 获赞 2 · 访问量 536

猜你喜欢

转载自blog.csdn.net/weixin_42741880/article/details/104750531