说起来装饰器,其实算是Python中比较难理解的知识点之一了,在Python开发的面试过程中,这是一个高频问题,甚至说必问问题都不为过,至少我经历的面试是这样。装饰器也是Python开发中经常要使用到的功能,熟练掌握装饰器会让你的代码更加简洁,思路也更加广阔。 好了,废话不多说,直接开锤。
什么是装饰器所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。理解起来好像就是装饰器本身可以增强其他函数的功能,一个函数平平无奇,没有关系,有我装饰器,定能让你变的闪闪发光。 这么说可能也有点绕,举个栗子,比如你是一个男程序员,然后你带上假发,穿上女装,再画个妆,那你可能会成为一个女装大佬,那么假发、女装、化妆就是装饰器,你还是你,但是因为有了这些装饰器,你成为了一个女装大佬。enmmmm...,大概就是这样。
下面看一个世界上最简单的函数,放宽心,绝对不是Hello World!
def hello(): print("你好,装饰器!")
哈哈,我没骗人吧,不信的小伙伴可以私下里运行一下哈哈哈。
那如果我想让hello()函数再实现个其他的功能,比如打印个执行函数时的时间。 有人说可以在hello()函数里加上打印时间的代码不就好了。
import datetime def hello(): print("当前时间:", datetime.datetime.now()) print("你好,装饰器!")
那么问题来了,如果还有hello1()、hello2()、hello3()等等大量的函数,那也一个一个的加上吗,那就会造成大量的重复代码,为了避免重复代码,我们可以重新定义一个函数,专门用来打印当前时间,打印完时间再执行真正的业务代码。
def print_time(func): print("当前时间:", datetime.datetime.now()) func() def hello(): print("你好,装饰器!") print_time(hello)
这样逻辑上虽然没多大毛病,但是这样的话,我们每次都要传递一个函数给print_time()函数,这种做法破坏了原有的代码逻辑结构,原来执行业务逻辑,我们只需要调用hello()函数,而现在我们必须要调用print_time()函数才行,那有没有更好的方式,当然,那就是装饰器。
装饰器的实现下面就是装饰器方式的实现:
import datetime def my_decorator(func): def wrapper(): print("当前时间:", datetime.datetime.now()) func() return wrapper @my_decorator def hello(): print("你好,装饰器!") hello()
运行结果:
当前时间: 2021-07-31 11:31:46.630720 你好,装饰器! Process finished with exit code 0
注:@符号是装饰器的语法糖,在定义函数的时候使用,可以避免再一次赋值 *** 作。
此处@my_decorator,相当于
hello = my_decorator(hello) hello()
很显然,这样不但可以实现需要的功能,代码也更加简洁。 解释一下,当运行最后的hello()函数时,调用过程是这样的:
- 先是执行hello = my_decorator(hello),此时变量hello指向的是my_decorator()。
- my_decorator(func)中传参是hello,返回的wrapper,而wrapper又会调用到原函数hello()。
- 所以,先执行wrapper()函数里的代码,然后才执行hello()函数里的。
上述代码里的my_decorator()就是一个装饰器,它“增强”了hello()的功能,但是并没有去真正的改变hello()函数的内部实现。
带参数的装饰器上面的只是一个非常简单的装饰器,但是实际场景中,很多函数都是要带有参数的,比如hello(name)等,那要怎么实现呢,其实也很简单,直接在对应装饰器的wrapper()上,加上对应的参数就可以了。
def my_decorator(func): def wrapper(*args, **kwargs): print("当前时间:", datetime.datetime.now()) func(*args, **kwargs) return wrapper @my_decorator def hello(name): print("你好,{}".format(name)) hello("tigeriaf")
输出:
当前时间: 2021-07-31 13:43:59.192468 你好,tigeriaf Process finished with exit code 0
注:在python里,*args和**kwargs表示接受任意数量和类型的参数,想要了解的可以看我之前的文章python中的*args和**kwargs用法解读
自定义参数上面的装饰器都是接收外来的参数,其实装饰器有自己的参数。 比如,加个参数来控制下业务函数执行的次数:
def my_decorator_outer(num): def my_decorator(func): def wrapper(*args, **kwargs): print("当前时间:", datetime.datetime.now()) for i in range(num): func(*args, **kwargs) return wrapper return my_decorator @my_decorator_outer(3) def hello(name): print("你好,{}".format(name)) hello("tigeriaf")
注意:这里是三层嵌套的函数,最外层my_decorator_outer函数的参数num控制的是次数。
运行后会输出以下信息:
当前时间: 2021-07-31 14:37:28.190077 你好,tigeriaf 你好,tigeriaf 你好,tigeriaf Process finished with exit code 0内置装饰器@functools.wrap
现在我们多做一步探索,我们来打印一下下面例子中的hello()函数的元信息:
def my_decorator(func): def wrapper(*args, **kwargs): print("当前时间:", datetime.datetime.now()) func(*args, **kwargs) return wrapper @my_decorator def hello(name): print("你好,{}".format(name)) # hello("tigeriaf") print(hello.__name__) # 打印hello函数的元信息
结果为:
wrapper Process finished with exit code 0
这说明,它不再是以前的那个hello()函数,而是被wrapper()函数取代了,函数名等函数属性会发生改变,当然这对结果不会产生什么影响,但是如果我们需要用到元函数信息,那要怎么保留它呢?哎,这时候可以用内置装饰器@functools.wrap来实现。
import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print("当前时间:", datetime.datetime.now()) func(*args, **kwargs) return wrapper @my_decorator def hello(name): print("你好,{}".format(name)) # hello("tigeriaf") print(hello.__name__)
运行下看看:
hello Process finished with exit code 0
显然,我们在写一个装饰器的时候,最好加上@functools.wrap,它能保留原有函数的名称和函数属性。
类装饰器刚刚我们接触的装饰器是函数来完成,由于Python的灵活性,我们也可以使用类来实现一个装饰器。
类能实现装饰器的功能,是由于当我们调用一个类的实例时,就调用到它的__call__()方法。
那接下来我们把之前的例子改成类装饰器来实现:
class MyDecorator: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("当前时间:", datetime.datetime.now()) return self.func(*args, **kwargs) @MyDecorator def hello(name): print("你好,{}".format(name)) hello("tigeriaf")
运行结果:
当前时间: 2021-07-31 15:06:15.400093 你好,tigeriaf Process finished with exit code 0多层装饰器的执行顺序
既然装饰器可以增强函数的功能,那如果有多个装饰器,我都想要怎么办?
其实,只要把需要用的装饰器都加上去就好了。
@decorator1 @decorator2 @decorator3 def hello(): ...
但是要注意的是,这里的执行顺序,会从上到下依次执行,可以来看下:
def my_decorator1(func): @functools.wraps(func) def wrapper(*args, **kwargs): print("装饰器1,当前时间:", datetime.datetime.now()) func(*args, **kwargs) return wrapper def my_decorator2(func): @functools.wraps(func) def wrapper(*args, **kwargs): print("装饰器2,当前时间:", datetime.datetime.now()) func(*args, **kwargs) return wrapper def my_decorator3(func): @functools.wraps(func) def wrapper(*args, **kwargs): print("装饰器3,当前时间:", datetime.datetime.now()) func(*args, **kwargs) return wrapper @my_decorator1 @my_decorator2 @my_decorator2 def hello(name): print("你好,{}".format(name)) hello("tigeriaf")
运行结果:
装饰器1,当前时间: 2021-07-31 15:13:23.824342 装饰器2,当前时间: 2021-07-31 15:13:23.824342 装饰器2,当前时间: 2021-07-31 15:13:23.824342 你好,tigeriaf Process finished with exit code 0总结
好了,关于装饰器就先说道这里,其实装饰器经常用于有切面需求的场景,比如:性能测试、添加日志、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与业务函数功能本身无关的重复代码,这样可以使整体更简洁。何乐而不为呢。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)