2.5-闭包,装饰器


闭包,装饰器

闭包

需求:

'''
用函数去实现:完成一个计算不断增加的系列值的平均值的需求。
例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,每天记录当天价格,然后计算他的平均值:平均值要考虑直至目前为止所有的价格。
比如大众推出了一款新车:小白轿车。
第一天价格为: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))	# 100000.0
print(make_averager(110000))	# 105000.0
print(make_averager(120000))	# 110000.0
# 已经能够实现功能了,但是代码相对来说不安全,因为series列表是一个全局变量,只要在全局作用域的任何地方,都可能对列表进行改变
series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)
print(make_averager(100000))        # 100000.0
print(make_averager(110000))        # 105000.0
series.append(1)
print(make_averager(120000))        # 82500.25
# 既然不能放在全局,放在函数中可以么,把series变成局部变量,那我们试一试
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))
# 100000.0
# 105000.0
# 110000.0
# 在函数中嵌套了一个函数,avg这个变量接收的实际是averager函数名,也就是其对应的内存地址,执行三次avg就是执行了三次averager这个函数;
# 为什么make_averager这个函数只执行了一次,为什么series这个列表没有消失还可以被调用三次呢?
# 形成闭包后,series变零就变成了自由变量,averager函数的作用域会延伸到包含自由变量series的绑定,也就是说,每次调用avg对应的averager函数时,都可以引用到这个自由变量series。这就是闭包;

闭包的定义:

  1. 闭包是嵌套在函数中的函数;
  2. 闭包必须是内层函数对外层函数的变量(非全局变量)的引用

如何判断闭包:

# 例一:
def wrapper():
    a = 1
    def inner():
        print(a)
    return inner
ret = wrapper()

# 例二:
a = 2
def wrapper():
    def inner():
        print(a)
    return inner
ret = wrapper()

# 例三:
def wrapper(a,b):
    def inner():
        print(a)
        print(b)
    return inner
a = 2
b = 3
ret = wrapper(a,b)

如果此函数拥有自由变量,name就可以证明是闭包函数;

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_varnames  查看函数的局部变量
print(avg.__code__.co_varnames)     # ('new_value', 'total')

print(avg.__closure__)  # 获取具体的自由变量对象,也就是cell对象
# (<cell at 0x0000024555617558: list object at 0x00000245556A8DC8>,)

# 自由变量具体的值
print(avg.__closure__[0].cell_contents)    #  []

闭包的作用:保存局部信息不被销毁,保证数据安全性;

闭包的应用:

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

装饰器

开放封闭原则

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

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

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

初始装饰器

需求1:写代码测试小明同学写的函数的执行效率;

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

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

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

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(2)
    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.010619640350342
# 欢迎访问黑色利穆主页
# 此函数的执行效率是:2.0116899013519287

这样重复代码太多了,想要解决重复代码,可以使用函数来减少代码

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def inner():
    start_time = time.time()
    index()
    end_time = time.time()
    print(f'此函数的执行效率是:{end_time - start_time}')
    
inner()

还是不行,虽然讲测试功能的代码封装成了一个函数,这样只能测小明的,不能测其他同事的

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def inner():
    start_time = time.time()
    index()
    home('黑色利穆')
    end_time = time.time()
    print(f'此函数的执行效率是:{end_time - start_time}')

inner()

这样做,再有新需求测试其他同事的代码时需要手动改。可以尝试函数传参;

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def inner(func):
    start_time = time.time()
    func()
    end_time = time.time()
    print(f'此函数的执行效率是:{end_time - start_time}')

inner(index)
# 这样将index函数的函数名作为参数传递给inner函数,然后inner函数里面执行index函数,就变成了动态传参;

真正的开放封闭原则:装饰器

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def timer(func):
    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函数返回给 f 变量;
# 执行f() 就相当于执行inner闭包函数,
# 问题:怎么解决原函数执行方式不变的问题。import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def timer(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'此函数的执行效率是:{end_time - start_time}')
    return inner
index = timer(index)
index()
# 这个timer就是最简单版本的装饰器

带返回值的装饰器

上述代码已经完成了最初版的装饰器,但是被装饰的函数index可能会有返回值,如果有返回值,装饰器不应该会影响

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def timer(func):
    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
# 因为现在的index不是函数名index,这个index实际是inner函数名,所以index()等同于inner()。
# 所以'访问成功'这个返回值应该返回给index才能做到开放封闭。现在实际上是返回给了func,
# 所以需要更改一下装饰器代码,让其返回值返回给index函数名;

def timer(func):
    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)
print(index())      # 访问成功
# 借助于内层函数inner,将func的返回值,返回给了inner函数的调用者,也就是函数外面的index,这样就实现了开放封闭原则。index返回值,返回给了'index';

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

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def timer(func):
    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('黑色利穆')
# 报错了:TypeError: inner() takes 0 positional arguments but 1 was given
# home这个变量是inner。home('黑色利穆')实际是inner('黑色利穆')。但是实参应该传递给home函数,实际上传递给了inner,所以要通过更改装饰器的代码,让实参传给home

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def timer(func):
    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('黑色利穆')
# 但是还有一个问题,现在被装饰函数的形参只有一个,如果有多个怎么处理;
# 可以接收不定数参数的形参来接收,*args, **kwargs

import time

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

def home(name):
    time.sleep(2)
    print(f'欢迎访问{name}主页')

def timer(func):
    def inner(*args, **kwargs):     # 函数定义时,*代表聚合:所以args = ('黑色利穆', 25)
        start_time = time.time()
        func(*args, **kwargs)      # 函数执行时,*代表打散,所以*args -- *('黑色利穆') -- func('黑色利穆')
        end_time = time.time()
        print(f'此函数的执行效率是:{end_time - start_time}')
    return inner
# timer装饰home函数
home = timer(home)
home('黑色利穆')
# 利用*的打散和聚合原理,将实参通过inner函数的中间完美传递给到相应的形参;

标准版装饰器

代码优化:

如果想给home加上装饰器,每次执行home前都要写上一句 home = timer(home) 这样在执行home('黑色利穆', 25)才是真正的添加了额外的功能,但是每次写这句话也很麻烦,python提供了简化机制,用@符号替代这句话

import time
def timer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(f'此函数的执行效率是:{end_time - start_time}')
    return inner

@timer
def home(name, age):
    time.sleep(3)
    print(name, age)
    print(f'欢迎访问{name}主页')
home('黑色利穆', 25)
# home函数如果想要加上装饰器,就在home函数上面加上@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():
    time.sleep(1)
    print('欢迎访问日记页面')

def comment():
    time.sleep(1)
    print('欢迎访问评论页面')

def home():
    time.sleep(1)
    print('欢迎访问博客园主页')
'''
import time

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
            login_status['username'] = username
            ret = func()
            return ret
    return inner

@auth
def diary():
    time.sleep(1)
    print('欢迎访问日记页面')
@auth
def comment():
    time.sleep(1)
    print('欢迎访问评论页面')
@auth
def home():
    time.sleep(1)
    name = login_status['username']
    print(f'欢迎访问{name}博客园主页')

diary()
comment()
home()
# 请输入用户名: 黑色利穆
# 请输入密码: 123
# 欢迎访问日记页面
# 欢迎访问评论页面
# 欢迎访问黑色利穆博客园主页

带参数的装饰器

装饰器其实就是一个闭包函数(两层函数),尽然是函数,就应该具有函数传参功能:

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
            login_status['username'] = username
            ret = func()
            return ret
    return inner
# 再套一层

def auth1(x):
    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
                login_status['username'] = username
                ret = func()
                return ret
        return inner
    return auth

举例:抖音,绑定的是微信账号密码;皮皮虾,绑定的是qq的账号密码;现在的需求是用装饰器分情况去判断账号和密码,不同的函数用的账号和密码来源不同;

之前的装饰器只能接受一个参数就是函数名,所以写一个可以接受参数的装饰器;

import time

login_status = {
    'username': None,
    'status': False,
}
def auth1(x):
    def auth(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
                    login_status['username'] = username
                    ret = func()
                    return ret
            if x == 'qq':
                username = input('请输入用户名: ').strip()
                password = input('请输入密码: ').strip()
                if username == '黑色利穆' and password == '456':
                    login_status['status'] = True
                    login_status['username'] = username
                    ret = func()
                    return ret
        return inner
    return auth

@auth1('WeChat')
def tik_tok():
    print('记录美好生活')

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

pipixia()


# @auth1('WeChat')
# 第一步先执行auth('WeChat')函数,得到返回值auth;
# 第二步@与auth结合,形成装饰器@auth,然后再一次执行

posted @ 2020-12-03 18:51  黑色利穆  阅读(71)  评论(0编辑  收藏  举报