python3 装饰器

1、开放封闭原则 

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

  2.对修改是封闭的就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对函数内部进行修改,或者修改了函数的调用方式,很有可能影响其他已经在使用该函数的用户。OK,理解了开封封闭原则之后,我们聊聊装饰器。

2、初识装饰器

  首先说说什么是装饰器呢?装饰器就是在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能

  接下来,我们通过一个例子来为大家讲解这个装饰器:

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

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

 版本1:

     需求分析:你要想测试此函数的执行效率,你应该怎么做?应该在此函数执行前记录一个时间, 执行完毕之后记录一个时间,这个时间差就是具体此函数的执行效率。那么执行时间如何获取呢? 可以利用time模块,有一个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}')

  由于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}')

   版本1分析:你现在已经完成了这个需求, 但是如果让你检测几十个几百个程序的效率,每个代码都要重新写代码来检测,重复代码太多了,所以要想解决重复代码的问题,我们还是需要函数来减少重复代码。

版本2


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)
 

     这样我将index函数的函数名作为参数传递给timmer函数,然后在timmer函数里面执行index函数,这样就变成动态传参了。但是我们改变了原函数的执行方式,所以继续改。

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

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

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()

  这个timer就是最简单版本的装饰器

3、带返回值的装饰器

  你现在这个代码,完成了最初版的装饰器,但是还是不够完善,因为你被装饰的函数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

  加上装饰器之后,函数的返回值变成了 NONE ,是因为此时的 index 不是函数名 index,而是而是函数名 inner,所以 index() 等同于 inner(),而“访问成功”则是返回给了函数名index,所以输出当然为None,所以我们要更改装饰器代码,让“访问成功”返回给函数名 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())

  将装饰器这样改了之后,创建了对象ret,使func 的返回值返回给了 inner 函数的调用者 index(),符合开放封闭原则。

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

  到目前为止,你的被装饰函数还是没有传参呢?按照我们的开放封闭原则,加不加装饰器都不能影响你被装饰函数的使用。所以我们看一下。

#错误示范!!!!!


import
time 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('太白')

  此时,参数“太白”,并没有传给外层函数home ,而是传给了inner , 但是我们必须保证参数能顺利的传给原函数,下面我们来改一下代码。

import time

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('太白')

  此时,func等于原函数名home ,所以在func()中加 name,即可将“太白”传给原函数名home 。但是原函数的形参不止一个怎么办呢?这个时候我们就要想到*args,**kwargs。

import time

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)

5、标准版装饰器

  代码优化:语法糖

  如果你想给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)

  如上,@timer就是语法糖,它代替了home = timer(home)这个繁琐的代码。

  致辞我们最终的装饰器模版就是这样的:

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

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

def auth():
​
 pass
​

​
def diary():
​ print('欢迎访问日记页面')
​

​
def comment():
​
 print('欢迎访问评论页面')
​

​
def home():
​
 print('欢迎访问博客园主页')
答案:

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 == '123':
            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
原文地址:https://www.cnblogs.com/490144243msq/p/11019722.html