17 装饰器

装饰器

1 回顾

1.1 *args, **kwargs

在形参中代表汇总,
*args:表示将多余的位置实参收集起来,以元组的形式赋值给形参args。
**kwargs:表示将多余的关键字实参收集起来,以字典的形式赋值给形参kwargs。

在实参中代表打散,
*args:表示将其后面的容器中存放的元素分解,转换为位置实参。
**kwargs:表示将其后面的字典中存放的元素分解,转换为关键字实参。

def inner(x, y, z):
    pass

def outer(*args, **kwargs):
    inner(*args, **kwargs)

outer(2, z=1, y=3)
'''
具体过程:
形参
outer(*args, **kwargs)  
args = (2, ) 
kwargs = {'y': 3, 'z': 1}
实参
inner(*args, **kwargs)
inner(*(2, ), **{'y': 3, 'z': 1})
inner(2, y=3, z=1)
'''
1.2 名称空间与作用域

对于函数,名称空间的构成是在解释器扫描语法时,在函数的定义阶段确定的。

1.3 函数对象

函数对象实际上存储的是存储函数体代码的内存空间地址。

函数对象可以是函数的参数
函数对象可以是函数的返回值

def func():
	pass
def outer(func):
	return func
1.4 函数嵌套定义
def outer():
	def inner():
		pass
1.5 闭包函数

闭:封闭函数,函数是内嵌函数,其名字处于局部名称空间,只能通过外部函数来间接调用。
包:在封闭的基础上,内嵌函数中包含对外层函数局部作用域中的变量的引用。

def outer():
    x = 1
	def inner():
		x
	return inner

inner = outer()

调用函数结束后,一般函数的局部变量应该被回收。
对于闭包函数,外层函数的局部变量不会被回收。
上面的例子中,当函数outer()执行后,其局部变量x不会被回收。
因为其返回值对应的函数对象inner指向的函数体代码中存在对变量x的引用。
即当一个函数的局部变量被其它作用域引用时,这个函数的局部变量不会被回收。

当一个函数体中需要参数,但不能通过形参的方式为其传入,
可以通过闭包的方式向函数体传入参数。

'''
假设此时不能通过形参的方式为函数inner的函数体中传递参数。
'''
def outer(x):
	def inner():
		x
	return inner

inner = outer(1)

2 装饰器

2.1 什么是装饰器

器,即工具,可以理解为函数。
装饰,意思是打扮美化等行为,这里可以理解为为其它事物增添额外的功能以达到美化的目的。
装饰器,定义一个函数,用来为其它函数增添额外的功能。

2.2 为什么使用装饰器

开放封闭原则
开放:指的是对拓展功能是开放的,
封闭:指的是对修改源代码是封闭的。
即拓展功能是以不修改原代码为前提的。
如果需要为一个函数扩展功能,不要修改函数体源代码和函数形参,包括为其增添新代码。

装饰器可以在不修改被装饰对象的源代码以及调用方式的基础上为被装饰对象添加新的功能。

2.3 实现思路

目前已存在一个函数 add_sleep(x, y)

import time

def add_sleep(x, y, sleep_time):
	time.sleep(sleep_time)
	return x + y

res = add_sleep(1, 2, 0.5)
print(res)

现在需要为其增添记录函数执行时间的功能。

方案1

import time

def add_sleep(x, y, sleep_time):
	start_time = time.time()
	time.sleep(sleep_time)
	stop_time = time.time()
	print(f'函数执行时间为{stop_time - start_time}')
	return x + y

res = add_sleep(1, 2, 0.5)
print(res)

方案1修改了函数的源代码,否定。

方案2
在原函数调用的时候增添新功能所需要的代码。

import time

def add_sleep(x, y, sleep_time):
	time.sleep(sleep_time)
	return x + y

start_time = time.time()
res = add_sleep(1, 2, 0.5)
stop_time = time.time()
print(f'函数执行时间为{stop_time - start_time}')
print(res)

对于方案2,调用一次这个函数就必须增添这几行代码。
这样做会增加重复代码,使文件代码冗余,使得代码的可维护性和可扩展性变差,文件结构变乱。

方案3
将可能被重复使用的代码封装成新的函数。

import time

def add_sleep(x, y, sleep_time):
	time.sleep(sleep_time)
	return x + y

def wrapper():
    start_time = time.time()
    res = add_sleep(1, 2, 0.5)
    stop_time = time.time()
    print(f'函数执行时间为{stop_time - start_time}')
    return res
 
wrapper()

对于方案3,原函数的调用方式被改变了,否定。

方案3 优化1
通过外部函数为内部函数传递参数。

import time

def add_sleep(x, y, sleep_time):
	time.sleep(sleep_time)
	return x + y

def wrapper(x, y, sleep_time):
    start_time = time.time()
    res = add_sleep(x, y, sleep_time)
    stop_time = time.time()
    print(f'函数执行时间为{stop_time - start_time}')
    return res
wrapper(1, 2, 0.5)

方案3 优化2
使用可变长度参数,使内部被装饰的函数参数可以任意变化而不影响外层的装饰函数。

import time

def add_sleep(x, y, sleep_time):
	time.sleep(sleep_time)
	return x + y

def wrapper(*args, **kwargs):
    start_time = time.time()
    res = add_sleep(*args, **kwargs)
    stop_time = time.time()
    print(f'函数执行时间为{stop_time - start_time}')
    return res
wrapper(1, 2, 0.5)

此时如果修改了原函数的参数列表,无需修改外层函数的参数列表。

方案3 优化3
优化2中只能装饰特定的函数,即wrapper()函数只能为add_sleep()这一个函数增添计时功能。为了能够使被装饰的对象可以改变,使得装饰器可以适用于其它对象。应该将被装饰的对象以参数的形式传给装饰器函数。

此时,wrapper()将接收到的参数原封不动地交给了其内部函数add_sleep(),因此为wrapper()传递的参数格式(位置,数量,关键字等)必须遵循其内部函数add_sleep()。

因此,这个装饰器函数体中需要被装饰的对象作为参数,但不能通过形参的方式为其传入,可以通过闭包的方式向函数体传入参数。

import time

def add_sleep(x, y, sleep_time):
	time.sleep(sleep_time)
	return x + y

print(add_sleep)  # <function add_sleep at 0x地址1>

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print(f'函数执行时间为{stop_time - start_time}')
        return res
    return wrapper
# 偷梁换柱
add_sleep = timer(add_sleep)
print(add_sleep)  # <function timer.<locals>.wrapper at 0x地址2>
# 即此时的变量add_sleep指向的是被装饰的wrapper()函数。
res = add_sleep(1, 2, 0.5)
print(res)


# 这里为另一个函数增添计时功能
def only_sleep(sleep_time):
	time.sleep(sleep_time)
# 偷梁换柱
only_sleep = timer(only_sleep)
only_sleep(1)
2.4 语法糖

语法糖 Syntactic sugar
指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,只是便于程序员使用。

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print(f'函数执行时间为{stop_time - start_time}')
        return res
    return wrapper

def add_sleep(x, y, sleep_time):
	time.sleep(sleep_time)
	return x + y

add_sleep = timer(add_sleep)  # 偷梁换柱
res = add_sleep(1, 2, 0.5)
print(res)


def only_sleep(sleep_time):
	time.sleep(sleep_time)

only_sleep = timer(only_sleep)  # 偷梁换柱
only_sleep(1)

对上面的方案3 优化3的代码做进一步优化,即可以省略偷梁换柱的语句。
在被装饰对象的上面一行增添 @ + 装饰器名称

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print(f'函数执行时间为{stop_time - start_time}')
        return res
    return wrapper


@timer  # 等价于add_sleep = timer(add_sleep)
def add_sleep(x, y, sleep_time):
    time.sleep(sleep_time)
    return x + y

res = add_sleep(1, 2, 0.5)
print(res)


@timer  # 等价于only_sleep = timer(only_sleep)
def only_sleep(sleep_time):
    time.sleep(sleep_time)
    
only_sleep(1)
2.5 模板
def deco(func):
    def wrapper(*args, **kwargs):
        # 1. 调用原函数
        # 2. 为原函数增加新的功能
        res = func(*args, **kwargs)
        return res
    return wrapper


@deco
def func():
	pass

3 练习

3.1 编写函数,函数执行的时间用time.sleep(n)模拟
def sleep(sleep_time):
	print('开始执行。')
    time.sleep(sleep_time)
    print('执行结束。')
3.2 编写装饰器,为函数加上统计时间的功能
def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print(f'程序运行时间为:{stop_time - start_time}')
        return res
    return wrapper

@timer
def sleep(sleep_time):
    print('开始执行。')
    time.sleep(sleep_time)
    print('执行结束。')

sleep(1)
3.3 编写装饰器,为函数加上认证的功能
import time


def read_file():
    user_dict = {}
    with open(r'./db.txt', mode='rt', encoding='utf-8') as f:
        for each_line in f:
            un, pw = each_line.strip().split(':')
            user_dict[un] = pw
    return user_dict


def check_login():
    user_dict = read_file()
    while 1:
        un_input = input('请输入用户名:').strip()
        if un_input not in user_dict:
            print('用户名不存在,请确认后重新输入。')
        else:
            pw = user_dict[un_input]
            break
    while 1:
        pw_input = input('请输入密码:').strip()
        if pw_input != pw:
            print('密码错误,请确认后重新输入。')
        else:
            print('登陆成功。')
            break


def auth(func):
    def wrapper(*args, **kwargs):
        check_login()
        res = func(*args, **kwargs)
        return res
    return wrapper


@auth
def sleep(sleep_time):
    print('开始执行。')
    time.sleep(sleep_time)
    print('执行结束。')


sleep(2)
3.4 编写装饰器,为多个函数加上认证的功能,用户的账号密码来源于文件 db.txt,要求登录成功一次,后续的函数都无需再输入用户名和密码。
import time

login_flg = 0

def read_file():
    user_dict = {}
    with open(r'./db.txt', mode='rt', encoding='utf-8') as f:
        for each_line in f:
            un, pw = each_line.strip().split(':')
            user_dict[un] = pw
    return user_dict

def check_login():
    global login_flg
    if login_flg:
        print('已登录。')
        return

    user_dict = read_file()
    while 1:
        un_input = input('请输入用户名:').strip()
        if un_input not in user_dict:
            print('用户名不存在,请确认后重新输入。')
        else:
            pw = user_dict[un_input]
            break
    while 1:
        pw_input = input('请输入密码:').strip()
        if pw_input != pw:
            print('密码错误,请确认后重新输入。')
        else:
            print('登陆成功。')
            login_flg = 1
            break

def auth(func):
    def wrapper(*args, **kwargs):
        check_login()
        res = func(*args, **kwargs)
        return res
    return wrapper

@auth
def func1(sleep_time):
    print('func1开始执行。')
    time.sleep(sleep_time)
    print('func1执行结束。')

@auth
def func2(sleep_time):
    print('func2开始执行。')
    time.sleep(sleep_time)
    print('func2执行结束。')

func1(1)
func2(1)
3.5 编写装饰器,为多个函数加上认证功能,要求登录成功一次,在超时时间内无需重复登录,超过了超时时间,则必须重新登录。
import time

TIME_INTERVAL = 5  # 超时时间 5秒
total_start_time = None  # 登陆成功时间

def read_file():
    user_dict = {}
    with open(r'./db.txt', mode='rt', encoding='utf-8') as f:
        for each_line in f:
            un, pw = each_line.strip().split(':')
            user_dict[un] = pw
    return user_dict

def check_login():
    global total_start_time
    if total_start_time is not None:
        if time.time() - total_start_time >= TIME_INTERVAL:
            total_start_time = None
        else:
            print('已登录。')
            return
    user_dict = read_file()
    while 1:
        un_input = input('请输入用户名:').strip()
        if un_input not in user_dict:
            print('用户名不存在,请确认后重新输入。')
        else:
            pw = user_dict[un_input]
            break
    while 1:
        pw_input = input('请输入密码:').strip()
        if pw_input != pw:
            print('密码错误,请确认后重新输入。')
        else:
            print('登陆成功。')
            total_start_time = time.time()
            break

def auth(func):
    def wrapper(*args, **kwargs):
        check_login()
        res = func(*args, **kwargs)
        return res
    return wrapper

@auth
def func1(sleep_time):
    print('func1开始执行。')
    time.sleep(sleep_time)
    print('func1执行结束。')

@auth
def func2(sleep_time):
    print('func2开始执行。')
    time.sleep(sleep_time)
    print('func2执行结束。')

func1(1)
func2(1)
time.sleep(5)
func1(1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值