装饰器

1.闭包

1.1给大家提个需求,然后用函数去实现:

完成一个计算不断增加的系列值的平均值的需求

例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,每天记录当天价格,然后计算他的平均值:平均值要考虑直至目前为止所有的价格

比如大众推出了一款新车:小白轿车

第一天价格为:100000元,平均收盘价:100000元

第二天价格为:110000元,平均收盘价:(100000 + 110000)/2 元

第三天价格为:120000元,平均收盘价:(100000 + 110000 + 120000)/3 元

........

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)

print(make_averager(100000))
print(make_averager(110000))
print(make_averager(120000))

从上面的例子可以看出,基本上完成了我们的要求,但是这个代码相对来说是不安全的,因为你的这个series列表是一个全局变量,只要是全局作用域的任何地方,都可能对这个列表进行改变

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)

print(make_averager(100000))
print(make_averager(110000))
series.append(666)  # 如果对数据进行相应改变,那么你的平均收盘价就会出现很大的问题。
print(make_averager(120000))

那么怎么办呢?有人说,你把他放在函数中不就行了,这样不就是局部变量了么?数据不就相对安全了么?

def make_averager(new_value):
    series = []
    series.append(new_value)
    total = sum(series)
    return total / len(series)

print(make_averager(100000))  # 100000.0
print(make_averager(110000))  # 110000.0
print(make_averager(120000))  # 120000.0

这样计算的结果是不正确的,那是因为执行函数,会开启一个临时的名称空间,随着函数的结束而消失,所以你每次执行函数的时候,都是重新创建这个列表,那么这怎么做呢?这种情况下,就需要用到我们讲的闭包了,我们用闭包的思想改一下这个代码

def make_averager():

    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averager
avg = make_averager()
print(avg(100000))
print(avg(110000))
print(avg(120000))

大家仔细看一下这个代码,我是在函数中嵌套了一个函数。那么avg 这个变量接收的实际是averager函数名,也就是其对应的内存地址,我执行了三次avg 也就是执行了三次averager这个函数。那么此时你们有什么问题?

1.2闭包的定义:

1.闭包是嵌套在函数中的函数

2.闭包必须是内层函数对外层函数的变量(非全局变量)的引用

def make_averager():
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averager
avg = make_averager()
# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)
# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)
# 函数名.__code__.co_varnames 查看函数的局部变量
print(avg.__code__.co_varnames)  # ('new_value', 'total')
# 函数名.__closure__ 获取具体的自由变量对象,也就是cell对象。
# (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,)
# cell_contents 自由变量具体的值
print(avg.__closure__[0].cell_contents)  # []

1.3闭包的作用:

保存局部信息不被销毁,保证数据的安全性

1.4闭包的应用:

可以保存一些非全局变量但是不易被销毁、改变的数据,装饰器

2.装饰器

2. 1开放封闭原则

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

​ 1.对扩展是开放的

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

​ 2.对修改是封闭的

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

​ 什么是装饰器?从字面意思来分析,先说装饰,什么是装饰? 装饰就是添加新的,

​ 比如我现在不会飞,怎么才能让我会飞?给我额外增加一个翅膀,我就能飞了。那么你给我加一个翅膀,它会改变我原来的行为么?我之前的吃喝拉撒睡等生活方式都不会改变。它就是在我原来的基础上,添加了一个新的功能。

今天我们讲的装饰器(翅膀)是以功能为导向的,就是一个函数。

被装饰的对象:我本人,其实也是一个函数。

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

2.2初识装饰器

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

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

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

版本1:

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

import time
print(time.time())

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

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

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

重复代码太多了,所以要想解决重复代码的问题,怎么做?我们是不是学过函数,函数就是以功能为导向,减少重复代码,好我们继续整改。

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

但是你这样写也是有问题的,你虽然将测试功能的代码封装成了一个函数,但是这样,你只能测试小明同学的的函数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()

你要是像上面那么做,每次测试其他同事的代码还需要手动改,这样是不是太low了?所以如何变成动态测试其他函数?我们是不是学过函数的传参?能否将被装饰函数的函数名作为函数的参数传递进去呢?

版本3:

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的代码快速练一遍。 大家练习完了之后,发现有什么问题么? 对比着开放封闭原则说: 首先,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}主页')

你将上面的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()

我们分析一下,代码,代码执行到这一行:f = timer(index) 先执行谁?看见一个等号先要执行等号右边, timer(index) 执行timer函数将index函数名传给了func形参。内层函数inner执行么?不执行,inner函数返回 给f变量。所以我们执行f() 就相当于执行inner闭包函数。 f(),这样既测试效率又执行了原函数,有没有问题?当然有啦!!版本4你要解决原函数执行方式不改变的问题,怎么做? 所以你可以把 f 换成 index变量就完美了! index = timer(index) index()带着同学们将这个流程在执行一遍,特别要注意 函数外面的index实际是inner函数的内存地址而不是index函数。

2.3标准版装饰器

代码优化:语法糖

根据我的学习,我们知道了,如果想要各给一个函数加一个装饰器应该是这样:

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)

如果你想给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函数如果想要加上装饰器那么你就在home函数上面加上@home,就等同于那句话 home = timer(home)。这么做没有什么特殊意义,就是让其更简单化,比如你在影视片中见过野战军的作战时由于不方便说话,用一些简单的手势代表一些话语,就是这个意思。

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

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

这个就是标准的装饰器,完全符合代码开放封闭原则。这几行代码一定要背过,会用。

2.4 带参数的装饰器

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

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

你看我上面的装饰器,不要打开,他可以不可在套一层:

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 == '123':
                login_status['status'] = True
                ret = func()
                return ret
        return inner
    return auth

​ 举例说明:抖音:绑定的是微信账号密码。 皮皮虾:绑定的是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 == '123':
                login_status['status'] = True
                ret = func()
                return ret
    return inner

@auth2
def jitter():
    print('记录美好生活')


@auth2
def pipefish():
    print('期待你的内涵神评论')

解决方式:

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 == '123':
                    login_status['status'] = True
                    ret = func()
                    return ret
            elif x == 'qq':
                username = input('请输入用户名:').strip()
                password = input('请输入密码:').strip()
                if username == '小冯' and password == '123':
                    login_status['status'] = True
                    ret = func()
                    return ret
        return inner
    return auth2

@auth('wechat')  
def jitter():
    print('记录美好生活')

@auth('qq')
def pipefish():
    print('期待你的内涵神评论')

@auth('wechat') :分两步:第一步先执行auth('wechat')函数,得到返回值auth2, 第二步@与auth2结合,形成装饰@auth2 然后在依次执行。

2.5装饰器装饰多个函数

def wrapper1(func):
    def inner1(*args,**kwargs):
        print("这是装饰器一开始")
        func(*args,**kwargs)
        print("这是装饰器一结束")
    return inner1

def wrapper2(func):  
    def inner2(*args,**kwargs):
        print("这是装饰器二开始")
        func(*args,**kwargs)
        print("这是装饰器二结束")
    return inner2


@wrapper1  
@wrapper2  
def func():
    print("这是被装饰的函数")

func()

打印结果:

这是装饰器一开始
这是装饰器二开始
这是被装饰的函数
这是装饰器二结束
这是装饰器一结束

Python规定多个装饰器装饰一个函数的时候先执行离被装饰的函数最近的装饰器

2.6博客园装饰器版

装饰器版一:

msg = """
1.请登录
2.请注册
3.进入文章页面
4.进入评论页面
5.进入日记页面
6.进入收藏页面
7.注销账号
8.退出整个程序
>>>
"""
login_dic = {
    "username":None,
    "flag":False,
    "count":3
}

def wrapper(func):
    def inner(*args,**kwargs):
        if login_dic["flag"]:
            func()
        else:
            while login_dic["count"]:
                user = input("username:")
                pwd = input("password:")
                if user == "alex" and pwd == "alex123":
                    login_dic["username"] = user
                    login_dic["flag"] = True
                    login_dic["count"] = 0
                    print("登录成功!")
                    func()
                else:
                    login_dic["count"] -= 1
                    print(f"用户名或密码错误!剩余次数{login_dic['count']}")
    return inner

def register():
    pass

@wrapper
def login():
    pass

@wrapper
def article():
    print("这是文章")

@wrapper
def comment():
    print("这是评论")

@wrapper
def log():
    print("这是日记")

@wrapper
def collect():
    print("这是收藏")

@wrapper
def out():
    login_dic["username"] = None
    login_dic["flag"] = False
    print("退出成功!")

func_dic = {
    "1":login,
    "2":register,
    "3":article,
    "4":comment,
    "5":log,
    "6":collect,
    "7":out,
    "8":exit,
}

while True:
    chose = input(msg)
    if chose in func_dic:
        login_dic["count"] = 3
        if chose == "1":
            login()
        else:
            func_dic[chose]()
    else:
        print("请正确输入内容!")

装饰器版二:

msg = """
1.请登录
2.请注册
3.进入文章页面
4.进入评论页面
5.进入日记页面
6.进入收藏页面
7.注销账号
8.退出整个程序
>>>
"""
login_dic = {
    "username":None,
    "flag":False,
    "count":3
}

def wrapper(func):
    def inner(*args,**kwargs):
        if login_dic["flag"]:
            func()
        else:
            login(func)
    return inner

def register():
    pass


def login(func=False):
    while login_dic["count"]:
        user = input("username:")
        pwd = input("password:")
        if user == "alex" and pwd == "alex123":
            login_dic["username"] = user
            login_dic["flag"] = True
            login_dic["count"] = 0
            print("登录成功!")
            if func:
                func()
        else:
            login_dic["count"] -= 1
            print(f"用户名或密码错误!剩余次数{login_dic['count']}")

@wrapper
def article():
    print("这是文章")

@wrapper
def comment():
    print("这是评论")

@wrapper
def log():
    print("这是日记")

@wrapper
def collect():
    print("这是收藏")

@wrapper
def out():
    login_dic["username"] = None
    login_dic["flag"] = False
    print("退出成功!")

func_dic = {
    "1":login,
    "2":register,
    "3":article,
    "4":comment,
    "5":log,
    "6":collect,
    "7":out,
    "8":exit,
}

while True:
    chose = input(msg)
    if chose in func_dic:
        login_dic["count"] = 3
        func_dic[chose]()
    else:
        print("请正确输入内容!")

个人比较偏向第二种写法,因为这样就可以在装饰器中添加多个额外的功能,可读性还高

posted @ 2019-07-24 14:49  626  阅读(327)  评论(0编辑  收藏  举报