16、装饰器

昨天学了很多函数方面的概念和知识其中有一个闭包的函数。很多人都对闭包的作用不是很清楚,今天我们就来认识一个新的知识点装饰器。它就是闭包函数的一个经典应用。

预习:

1、编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
注意:从文件中读出字符串形式的字典,可以用eval('{"name":"egon","password":"123"}')转成字典格式
2、编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果
3、编写装饰器,实现缓存网页内容的功能:
具体:实现下载的页面存放于文件中,如果文件内有此网页,就优先从文件中读取网页内容,否则,就去下载,然后存到文件中
预习题

 

本篇导航:

 

一、楔子

def func1():
    print('in func1')

想要计算上面函数的执行时间:

import time
def func1():
    start = time.time()
    print('in func1')
    end =print(time.time() - end) 
func1()

可是如果你要计算你写过的所有函数的执行时间就要挨个去改代码。写个函数可不可以:

import time
def timer(func):
    start = time.time()
    func()
    print(time.time() - start)

def func1():
    print('in func1')


def func2():
    print('in func2')

timer(func1)
timer(func2)

不管我们写了多少个函数都可以调用这个计时函数来计算函数的执行时间了。。。尽管现在修改成本已经变得很小很小了,但是还是改变了这个函数的调用方式,如果有很多函数还是要改很多

你要做的就是,让你的同事依然调用func1,但是能实现调用timer方法的效果

import time
def timer(func):
    start = time.time()
    func()
    print(time.time() - start)

def func1():
    print('in func1')

func1 =timer(func1)  #报错
func1()

非常可惜,上面这段代码是会报错的,因为timer方法需要传递一个func参数,我们不能在赋值的时候传参,因为只要执行func1 = timer(func1),timer方法就直接执行了,下面的那句func1根本就没有意义。


 

二、装饰器的形成

器即函数

装饰即修饰,意指为其他函数添加新功能

装饰器定义:本质就是函数,功能是为其他函数添加新功能

import time

def func1():
    print('in func1')

def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

func1 = timer(func1)
func1()
装饰器——简单版1

现在唯一麻烦就是还要在做一次赋值调用。。。python的开发者也觉得碍眼,所以就为我们提供了一句语法糖来解决这个问题!

import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

@timer   #==> func1 = timer(func1)
def func1():
    print('in func1')


func1()
装饰器——语法糖

到这里,我们可以简单的总结一下:

装饰器的本质:一个闭包函数

装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展

最后一个问题要解决,刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办

def timer(func):
    def inner(a):
        start = time.time()
        func(a)
        print(time.time() - start)
    return inner

@timer
def func1(a):
    print(a)

func1(1)
装饰器——带参数的装饰器

假如你有两个函数,需要传递的参数不一样

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func1 = timer(func1)
def func1(a,b):
    print('in func1')

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))
装饰器—万能装饰器

如果你的函数是有返回值的

import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func2('aaaaaa','bbbbbb')
print(func2('aaaaaa'))
装饰器—带返回值的装饰器

 

三、开放封闭原则

1、对扩展是开放的

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

2、对修改是封闭的

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

装饰器完美的遵循了这个开放封闭原则。


 

四、装饰器的主要功能和装饰器的固定结构

装饰器的主要功能:

在不改变函数调用方式的基础上在函数的前、后添加功能。

装饰器的固定格式:

def timer(func):
    def inner(*args,**kwargs):
        '''执行函数之前要做的'''
        re = func(*args,**kwargs)
        '''执行函数之后要做的'''
        return re
    return inner
装饰器的固定格式

 

五、带参数的装饰器和多个装饰器装饰同一个函数

1、有成千上万个函数使用了一个装饰器,现在你想把这些装饰器都取消掉

def outer(flag):
    def timer(func):
        def inner(*args,**kwargs):
            if flag:
                print('''执行函数之前要做的''')
            re = func(*args,**kwargs)
            if flag:
                print('''执行函数之后要做的''')
            return re
        return inner
    return timer

@outer(False)
def func():
    print(111)

func()
带参数的装饰器

2、有些时候,我们也会用到多个装饰器装饰同一个函数的情况

def pipixia(func):
    def inner():
        print('wrapper1 ,before func')
        func()
        print('wrapper1 ,after func')
    return inner

def qqxing(func):
    def inner():
        print('wrapper2 ,before func')
        func()
        print('wrapper2 ,after func')
    return inner

@qqxing
@pipixia
def f():
    print('in f')

f()
多个装饰器装饰同一个函数

多个装饰器装饰同一个函数执行顺序:


 

思维导图:

 

预习解答:

#编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,
#后续的函数都无需再输入用户名和密码
# tag = True
tag = [True]
def rz(func) :
    def inner() :
        # global tag
        # while tag :
        while tag[0] :
            name = input("please input your name:").strip()
            passworld = input("please input your passworld:").strip()
            with open("demo.py", 'r', encoding='utf-8') as read_f :
                print()
                if read_f.readline() == name+"\n" and read_f.readline() == passworld+"\n" :
                    # tag = False
                    tag[0] = False
        func()
    return inner

@rz
def func() :
    print("lln")
@rz
def func1() :
    print("lln1")
@rz
def func2() :
    print("lln2")

func()
func1()
func2()




#方法二:(推荐)
auth_status = {
    'user':None,
    'status':False
}

def auth(func):
    def inner(*args,**kwargs):
        if auth_status['status']:
            ret = func(*args, **kwargs)  # index/home
            return ret
        else:
            username = input('username : ').strip()
            password = input('password : ').strip()
            # 读文件获取用户信息
            f = open('conf')
            user_info = f.read()
            user_dic = eval(user_info)
            if user_dic.get(username) and user_dic[username] == password:
                print('login successful')
                auth_status['user'] = username
                auth_status['status'] = True
                ret = func(*args,**kwargs)  #index/home
                return ret
            else:
                print('login failed')
    return inner

@auth
def index():
    print("欢迎来到首页")

@auth
def home():
    print("欢迎回家")

index()
index()
home()
index()
预习1答案

 

url_l = []
from urllib.request import urlopen

def get_cache(func):
    def inner(*args,**kwargs):
        url = args[0]
        filename = str(hash(url))
        if url in url_l:
            f = open(filename,'rb')
            ret = f.read()
        else:
            url_l.append(url)
            ret = func(*args, **kwargs)
            f = open(filename,'wb')
            f.write(ret)
        f.close()
        return ret
    return inner

@get_cache
def get(url):
    return urlopen(url).read()

print(get('http://www.cnblogs.com/liluning/'))
print(get('http://www.cnblogs.com/liluning/'))
print(get('http://www.cnblogs.com/liluning/'))
print(get('http://www.cnblogs.com/liluning/'))
print(get('http://www.cnblogs.com/liluning/'))
预习2答案

 

 

posted @ 2017-07-27 20:16  布吉岛丶  阅读(406)  评论(0编辑  收藏  举报