python基础十——初识装饰器

开放封闭原则

       软件一旦上线之后(比如你的软件主要是多个函数组成的),那么这个软件对功能的扩展应该是开放的,比如你的游戏一直在迭代更新,推出新的玩法,新功能。但是对于源代码的修改是封闭的。你就拿函数举例,如果你的游戏源代码中有一个函数是闪躲的功能,那么你这个函数肯定是被多个地方调用的,比如对方扔雷,对方开枪,对方用刀,你都会调用你的闪躲功能,那么如果你的闪躲功能源码改变了,或者调用方式改变了,当对方发起相应的动作,你在调用你的闪躲功能,就会发生问题。所以,开放封闭原则具体具体定义是这样:

    1.对扩展是开放的

        我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。

    2.对修改是封闭的

        就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对函数内部进行修改,或者修改了函数的调用方式,很有可能影响其他已经在使用该函数的用户。

装饰器定义就是:在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能

初识装饰器

示例:

需求介绍:你现在xx科技有限公司的开发部分任职,领导给你一个业务需求让你完成:让你写代码测试小明同学写的函数的执行效率

def index():
    print('欢迎访问博客园主页')

版本1:

    需求分析:你要想测试此函数的执行效率,你应该在此函数执行前记录一个时间, 执行完毕之后记录一个时间,这个时间差就是具体此函数的执行效率。 可以利用time模块,有一个time.time()功能

import time
print(time.time())

      此方法返回的是格林尼治时间,是此时此刻距离1970年1月1日0点0分0秒的时间秒数。也叫时间戳,他是一直变化的。所以要是计算shopping_car的执行效率就是在执行前后计算这个时间戳的时间,然后求差值即可。

import time
def index():
    print('欢迎访问博客园主页')
​
start_time = time.time()
index()
end_time = time.time()
print(f'此函数的执行效率为{end_time-start_time}')
View Code

由于index函数只有一行代码,执行效率太快了,所以我们利用time模块的一个sleep模拟一下

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
​
start_time = time.time()
index()
end_time = time.time()
print(f'此函数的执行效率为{end_time-start_time}')
View Code

 版本1分析:你现在已经完成了这个需求,但是有什么问题没有? 虽然你只写了四行代码,但是你完成的是一个测试其他函数的执行效率的功能,如果让你测试一下,小张,小李,小刘的函数效率呢? 你是不是全得复制:

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园首页')
​
def home(name):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(f'欢迎访问{name}主页')
​
start_time = time.time()
index()
end_time = time.time()
print(f'此函数的执行效率为{end_time-start_time}')
​
start_time = time.time()
home('辉煌')
end_time = time.time()
print(f'此函数的执行效率为{end_time-start_time}')
View Code

函数就是以功能为导向,减少重复代码,继续整改

 版本2:

import time
​
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
​
def inner():
    start_time = time.time()
    index()
    end_time = time.time()
    print(f'此函数的执行效率为{end_time-start_time}')
​
inner()
View Code

这样只能测试小明同学的的函数index,你要是测试其他同事的函数呢?

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
​
def home(name):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(f'欢迎访问{name}主页')
​
def inner():
    start_time = time.time()
    index()
    home('太白')
    end_time = time.time()
    print(f'此函数的执行效率为{end_time-start_time}')
​
timer()
View Code

这样每次测试其他同事的代码还需要手动改,这样是不是太low了,如何变成动态测试其他函数?

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
​
def home(name):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(f'欢迎访问{name}主页')
​
def timmer(func):  # func == index 函数
    start_time = time.time()
    func()  # index()
    end_time = time.time()
    print(f'此函数的执行效率为{end_time-start_time}')
​
timmer(index)
View Code

      将index函数的函数名作为参数传递给timmer函数,然后在timmer函数里面执行index函数,这样就变成动态传参了。好,你们现在将版本3的代码快速练一遍。 大家练习完了之后,发现有什么问题么? 对比着开放封闭原则说: 首先,index函数除了完成了自己之前的功能,还增加了一个测试执行效率的功能,对不?所以也符合开放原则。 其次,index函数源码改变了么?没有,但是执行方式改变了,所以不符合封闭原则。 原来如何执行? index() 现在如何执行? inner(index),这样会造成什么问题? 假如index在你的项目中被100处调用,那么这相应的100处调用我都得改成inner(index)。 非常麻烦,也不符合开放封闭原则。

 版本4:实现真正的开放封闭原则:装饰器

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
​
def home(name):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(f'欢迎访问{name}主页')
View Code

将上面的inner函数在套一层最外面的函数timer,然后将里面的inner函数名作为最外面的函数的返回值,这样简单的装饰器就写好了

def timer(func):  # func = index
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
​
# f = timer(index)
​
# f()
View Code

      代码,代码执行到这一行:f = timer(index) 先执行谁?看见一个等号先要执行等号右边, timer(index) 执行timer函数将index函数名传给了func形参。内层函数inner执行么?不执行,inner函数返回 给f变量。所以执行f() 就相当于执行inner闭包函数。 f(),这样既测试效率又执行了原函数,版本4你要解决原函数执行方式不改变的问题,怎么做? 所以可以把 f 换成 index变量就完美了! index = timer(index) index(),将这个流程在执行一遍,特别要注意 函数外面的index实际是inner函数的内存地址而不是index函数。这个timer就是最简单版本装饰器,在不改变原index函数的源码以及调用方式前提下,为其增加了额外的功能,测试执行效率。

 带返回值的装饰器

如果被修饰函数带有返回值,要实现开发封闭原则,装饰器也要返回值

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
    return '访问成功'
​
def timer(func):  # func = index
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
​
index = timer(index)
print(index())  # None
View Code

加上装饰器之后,他的返回值为None,因为现在的index不是函数名index,这index实际是inner函数名。所以index() 等同于inner()  '访问成功'返回值应该返回给谁?应该返回给index,这样才做到开放封闭,实际返回给了谁?实际返回给了func,所以要更改一下你的装饰器代码,让其返回给外面的index函数名。

def timer(func):  # func = index
    def inner():
        start_time = time.time()
        ret = func()
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
        return ret
    return inner
​
index = timer(index)  # inner
print(index())  # print(inner())
View Code

借助于内层函数inner,将func的返回值,返回给了inner函数的调用者也就是函数外面的index,这样就实现了开放封闭原则,index返回值,确实返回给了'index'

被装饰函数带参数的装饰器

根据开发封闭原则,加不加装饰器都不能影响函数运行

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
    return '访问成功'
​
def home(name):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(f'欢迎访问{name}主页')
​
def timer(func):  # func = index
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
​
# 要想timer装饰home函数怎么做?
home = timer(home)
home('辉煌')
View Code

报错了 !home这个变量是谁?是inner,home('辉煌')实际是inner('辉煌')但是你的'辉煌'这个实参应该传给谁?应该传给home函数,实际传给了谁?实际传给了inner,所以我们要通过更改装饰器的代码,让其将实参'辉煌'传给home.

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
    return '访问成功'
​
def home(name):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(f'欢迎访问{name}主页')
​
def timer(func):  # func = home
    def inner(name):
        start_time = time.time()
        func(name)  # home(name) == home('太白')
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
​
# 要想timer装饰home函数怎么做?
home = timer(home)
home('辉煌')
View Code

装饰器也适用*args,**kwargs这两个参数

import time
def index():
    time.sleep(2)  # 模拟一下网络延迟以及代码的效率
    print('欢迎访问博客园主页')
    return '访问成功'
​
def home(name,age):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(name,age)
    print(f'欢迎访问{name}主页')
​
def timer(func):  # func = home
    def inner(*args,**kwargs):  # 函数定义时,*代表聚合:所以你的args = ('辉煌',18)
        start_time = time.time()
        func(*args,**kwargs)  # 函数的执行时,*代表打散:所以*args --> *('辉煌',18)--> func('辉煌',18)
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
​
​
​
home = timer(home)
home('辉煌',18)
View Code

利用*的打散与聚合的原理,将这些实参通过inner函数的中间完美的传递到给了相应的形参

标准版装饰器

代码优化:语法糖

def home(name,age):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(name,age)
    print(f'欢迎访问{name}主页')
​
def timer(func):  # func = home
    def inner(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
​
home = timer(home)
home('辉煌',18)
View Code

如果想给home加上装饰器,每次执行home之前都要写上一句:home = timer(home)这样在执行home函数 home('辉煌',18) 才是真生的添加了额外的功能。但是每次写这一句也是很麻烦。所以,Python给我们提供了一个简化机制,用一个很简单的符号去代替这一句话。

def timer(func):  # func = home
    def inner(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
​
@timer  # home = timer(home)
def home(name,age):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(name,age)
    print(f'欢迎访问{name}主页')
​
home('辉煌',18)
View Code

调整了一下位置,要是不把装饰器放在上面,timer是找不到的。home函数如果想要加上装饰器就在home函数上面加上@home,就等同于那句话 home = timer(home)。

至此标准版的装饰器就是这个样子:

def wrapper(func):
    def inner(*args,**kwargs):
        '''执行被装饰函数之前的操作'''
        ret = func
        '''执行被装饰函数之后的操作'''
        return ret
    return inner
View Code

此时要利用这个装饰器完成一个需求:简单版模拟博客园登录。 此时要看一下博客园,说一下需求: 博客园登陆之后有几个页面,diary,comment,home,如果我要访问这几个页面,必须验证是否已登录。 如果已经成功登录,那么这几个页面我都可以无阻力访问。如果没有登录,任何一个页面都不可以访问,必须先登录,登录成功之后,才可以访问这个页面。用成功执行函数模拟作为成功访问这个页面,现在写三个函数,写一个装饰器,实现上述功能。

login_status = {
    'username': None,
    'status': False,
}
​
def auth(func):
    def inner(*args,**kwargs):
        if login_status['status']:
            ret = func()
            return ret
        username = input('请输入用户名:').strip()
        password = input('请输入密码:').strip()
        if username == '辉煌' and password == '123456':
            login_status['status'] = True
            ret = func()
            return ret
    return inner
​
@auth
def diary():
    print('欢迎访问日记页面')
​
@auth
def comment():
    print('欢迎访问评论页面')
​
@auth
def home():
    print('欢迎访问博客园主页')
​
diary()
comment()
home()
​
View Code

带参数的装饰器

装饰器其实就是一个闭包函数,再说简单点就是两层的函数。那么是函数,就应该具有函数传参功能。

login_status = {
    'username': None,
    'status': False,
}
​
def auth(func):
    def inner(*args,**kwargs):
        if login_status['status']:
            ret = func()
            return ret
        username = input('请输入用户名:').strip()
        password = input('请输入密码:').strip()
        if username == '辉煌' and password == '123456':
            login_status['status'] = True
            ret = func()
            return ret
    return inner
View Code

上面的装饰器,再套一层:

def auth(x):
    def auth2(func):
        def inner(*args,**kwargs):
            if login_status['status']:
                ret = func()
                return ret
            username = input('请输入用户名:').strip()
            password = input('请输入密码:').strip()
            if username == '辉煌' and password == '123456':
                login_status['status'] = True
                ret = func()
                return ret
        return inner
    return auth
View Code

 举例说明:抖音:绑定的是微信账号密码。 皮皮虾:绑定的是qq的账号密码。 现在要完成的就是你的装饰器要分情况去判断账号和密码,不同的函数用的账号和密码来源不同。 但是之前写的装饰器只能接受一个参数就是函数名,所以你写一个可以接受参数的装饰器。

def auth2(func):
    def inner(*args, **kwargs):
        if login_status['status']:
            ret = func()
            return ret
        if 微信:
            username = input('请输入用户名:').strip()
            password = input('请输入密码:').strip()
            if username == '太白' and password == '123':
                login_status['status'] = True
                ret = func()
                return ret
        elif 'qq':
            username = input('请输入用户名:').strip()
            password = input('请输入密码:').strip()
            if username == '辉煌' and password == '123456':
                login_status['status'] = True
                ret = func()
                return ret
    return inner
​
@auth2
def jitter():
    print('记录美好生活')
​
​
@auth2
def pipefish():
    print('期待你的内涵神评论')
View Code

解决方式:

def auth(x):
    def auth2(func):
        def inner(*args, **kwargs):
            if login_status['status']:
                ret = func()
                return ret
            
            if x == 'wechat':
                username = input('请输入用户名:').strip()
                password = input('请输入密码:').strip()
                if username == '辉煌' and password == '123456':
                    login_status['status'] = True
                    ret = func()
                    return ret
            elif x == 'qq':
                username = input('请输入用户名:').strip()
                password = input('请输入密码:').strip()
                if username == '辉煌' and password == '12345':
                    login_status['status'] = True
                    ret = func()
                    return ret
        return inner
    return auth2
    
@auth('wechat')  
def jitter():
    print('记录美好生活')
​
@auth('qq')
def pipefish():
    print('期待你的评论')
View Code

@auth('wechat') :分两步:

    第一步先执行auth('wechat')函数,得到返回值auth2

    第二步@与auth2结合,形成装饰器@auth2 然后在依次执行。

这样就是带参数的装饰器,参数可以传入多个,一般带参数的装饰器在工作中都使用的比较多

原文地址:https://www.cnblogs.com/huihuangyan/p/13612833.html