python高级之装饰器

装饰器

image

前言

装饰器就是类似于女孩子的发卡。你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡。但是你喜欢的女孩子还是你喜欢的女孩子。如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该可以给你玩,该打电话打电话,该玩游戏玩游戏。而你的手机就变成了带手机壳的手机。

什么是装饰器

装饰器,顾名思义就是装饰XXX的工具。在python中,装饰器的本质就是一个高阶函数,它接受一个函数作为参数,并返回一个被装饰后的函数。
装饰器的作用如下:
--在不修改被装饰函数的源代码和调用方式的情况下,给被装饰函数添加额外的功能。
--即就是你传一个函数给装饰器,装饰器不会改变该函数的代码和调用方式就能使该函数获得额外的功能。

为什么要使用装饰器

--装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。
装饰器的使用符合了面向对象编程的开放封闭原则。

--开放封闭原则主要体现在两个方面:
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

装饰器的实现

实现原理

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是'函数嵌套+闭包+函数对象'的组合使用的产物。

实现无参装饰器

无参装饰器模板

# 通过参数 func 接收外部的函数地址
def wrapper(func):
    def inner():
        # 第一部分:执行外部传入的函数之前执行的代码
        '''...'''
        # 第二部分:执行外部传入的函数地址(res接受外部函数的返回值,如果有返回值的情况下)
        result = func()
        # 第三部分:执行外部传入的函数之后执行的代码
        '''...'''
        return result
    return inner

一般写法

# 为函数添加一个统计运行时长的功能
import time
 
def how_much_time(func):
    def inner():
        t_start = time.time()
        func()
        t_end = time.time()
        print("一共花费了{0}秒时间".format(t_end - t_start, ))
    return inner
    # 将增加的新功能代码以及被装饰函数运行代码func()一同打包返回,返回的是一个内部函数,这个被返回的函数就是装饰器
 
def sleep_5s():
    time.sleep(5)
    print("%d秒结束了" % (5,))
 
def sleep_6s():
    time.sleep(6)
    print("%d秒结束了" % (6,))
 
sleep_5s = how_much_time(sleep_5s)
# 因为sleep_5s函数的功能就是睡5秒钟,虽然增加了统计运行时间的功能,但是他本身功能没变(还是睡5秒钟),所以仍然用原来函数名接收增加功能了的自己
sleep_6s = how_much_time(sleep_6s)
 
t1 = threading.Thread(target=sleep_5s)
t2 = threading.Thread(target=sleep_6s)
t1.start()
t2.start()
# 5秒结束了
# 一共花费了5.014161109924316秒时间
# 6秒结束了
# 一共花费了6.011810302734375秒时间

标准的语法糖写法

# 为函数添加一个统计运行时长的功能
import time
import threading
 
def how_much_time(func):
    def inner():
        t_start = time.time()
        func()
        t_end = time.time()
        print("一共花费了{0}秒时间".format(t_end - t_start, ))
 
    return inner
 
 
@how_much_time
# @how_much_time等价于sleep_5s = how_much_time(sleep_5s)
def sleep_5s():
    time.sleep(5)
    print("%d秒结束了" % (5,))
 
@how_much_time
def sleep_6s():
    time.sleep(6)
    print("%d秒结束了" % (6,))
 
t1 = threading.Thread(target=sleep_5s)
t2 = threading.Thread(target=sleep_6s)
t1.start()
t2.start()
# 5秒结束了
# 一共花费了5.087267637252808秒时间
# 6秒结束了
# 一共花费了6.0112926959991455秒时间

补充:给一个函数添加两个装饰器

# 为函数添加一个统计运行时长的功能以及日志记录功能
import time
import threading
 
def how_much_time(func):
    print("how_much_time函数开始了")
    def inner():
        t_start = time.time()
        func()
        t_end = time.time()
        print("一共花费了{0}秒时间".format(t_end - t_start, ))
    return inner
 
def mylog(func):
    print("mylog函数开始了")
    def inner_1():
        print("start")
        func()
        print("end")
    return inner_1
 
@mylog
@how_much_time
# 等价于mylog(how_much_time(sleep_5s))
def sleep_5s():
    time.sleep(5)
    print("%d秒结束了" % (5,))
 
if __name__ == '__main__':
    sleep_5s()
#how_much_time函数开始了
#mylog函数开始了
#start
#5秒结束了
#一共花费了5.012601613998413秒时间
#end
当一个函数具有两个装饰器时的执行顺序是:
    1,第一步先执行how_much_time函数的外部代码;
    2,第二步执行mylog函数的外部代码;
    3,第三步执行mylog的内部函数代码;
    4,第四步执行how_much_time函数的内部函数$代码

实现有参装饰器

了解无参装饰器的实现原理后,我们可以再实现一个用来为被装饰对象添加认证功能的装饰器,实现的基本形式如下:

def deco(func):
    def wrapper(*args,**kwargs):
        编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
    return wrapper

如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下

def deco(func):
        def wrapper(*args,**kwargs):
            if driver == 'file':
                编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
            elif driver == 'mysql':
                编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
        return wrapper

函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到

def auth(driver):
    def deco(func):
        ……
    return deco

此时我们就实现了一个有参装饰器,使用方式如下

先调用auth_type(driver='file'),得到@deco,deco是一个闭包函数,
包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样
@auth(driver='file') 
def index():     
    pass
@auth(driver='mysql') 
def home():
    pass  

可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释

@timer
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page',name)

print(help(home))
'''
打印结果:

Help on function wrapper in module __main__:

wrapper(*args **kwargs)

None

在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器

def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    wrapper.__doc__=func.__doc__
    wrapper.__name__=func.__name__
    return wrapper

按照上述方式来实现保留原函数属性过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下

from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper
posted @ 2023-12-12 22:15  Xiao0101  阅读(30)  评论(0)    收藏  举报