Python“三大器”之装饰器

一、装饰器

1、什么是装饰器?

  装饰:装饰、修饰

  器:工具

  装饰器:装饰的工具

 

  (*****)“开放封闭”:装饰器必须要遵循“开放封闭”原则:

    开放:对函数功能的添加是开放的

    封闭:对函数功能的修改是封闭的

 

2、装饰器的作用?

  在不修改被装饰对象源代码与调用方式的前提下添加新的功能。

  装饰器必须遵循的两个原则:

    不修改被装饰对象源代码

    不修改被装饰对象的调用方式

 

ps:

  被装饰对象指的是  --->  需要添加功能的(函数)

  装饰器指的是  --->  被装饰对象添加的新功能的(函数)

 

3、为什么要使用装饰器?

  可以解决代码荣誉问题,提高代码的可扩展性

 

4、怎么使用装饰器?

  编写装饰器:

    通过闭包函数来实现装饰器

  装饰器的应用:

    1、统计时间

    2、登录认证

 

例子:

  需求:需要统计一下下载电影的时间

    方案一:函数调用(适合少次使用)

import time

def download_movie():
    print("开始下载电影...")
    # 模拟电影下载时间为3秒
    time.sleep(3)
    print("电影下载成功!")

start_time = time.time()    # 获取当前时间戳
download_movie()
end_time = time.time()    # 获取当前时间戳
print(f"消耗时间:{end_time - start_time}")

  问题1:如果有多个被装饰对象,需要写多次统计时间的代码,导致代码冗余,于是有了方案二。

 

    方案二:使用装饰器(适合多次调用,调用时直接在闭包函数内传入所需要使用装饰器的函数名称)

import time

def download_movie():
    print("开始下载电影...")
    # 模拟电影下载时间为3秒
    time.sleep(3)
    print("电影下载成功!")

# 模拟多个被装饰对象
def func1():
    pass

# 模拟多个被装饰对象   
def func2():
    pass

# 装饰器:初级版
def time_record(func):
    def inner():
        # 获取当前时间戳
        start_time = time.time()
        func()
        # 获取当前时间戳
        end_time = time.time()
        print(f"消耗时间:{end_time - start_time}")
    return inner

download_movie = time_record(download_movie)
download_movie()

  问题2:如果被装饰对象有返回值,有参数而且有多个参数,于是有了方案三。

 

    方案三:方案二的代码优化,使用*args, **kwargs接收所有参数(形参,关键字参数等)import time

def download_movie(*args, **kwargs):
    print("开始下载电影...")
    # 模拟电影下载时间
    time.sleep(3)
    print("电影下载成功!")
    return "movie.mp4"

# 装饰器最终版
def time_record(func):
    def inner(*args, **kwargs):    # *args **kwargs接收所有参数(形参,关键字参数等)
        # 获取当前时间戳
        start_time = time.time()
        # 将被装饰对象需要接收的人以参数,原封不动的传给func(被修饰对象)
        res = func(*args, **kwargs)    #此处的fanc为被修饰对象download_movie()
        # 获取当前时间戳
        end_time = time.time()
        # 统计结束,打印统计时间
        print(f"消耗时间:{end_time - start_time}")
        return res
    return inner

# 这里的download_movie是inner()的返回值
download_movie = time_record(download_movie)
# download_movie()  --->  inner() ,可传入任意参数,若传入参数,download_movie()函数需要修改代码块,否则不打印或者不执行传入参数
download_movie()

  方案三完美的解决了方案一和方案二遗留下的问题,此处的装饰器可以作为所有装饰器的模板

 

  (*****)装饰器模板(记牢)

def wrapper(func):

    def inner(*args, **kwargs):    # *args **kwargs接收所有参数(形参,关键字参数等)

        # 调用被装饰对象,得到被装饰对象的返回值res
        res = func(*args, **kwargs)

        return res

    return inner

 

  装饰器简例:

def wrapper(func):
    def inner(*args, **kwargs):
        '''
        在此处可以添加新添加的功能代码块
        '''

        # 调用被装饰对象,得到被装饰对象的返回值res
        res = func(*args, **kwargs)

        '''
        在此处也可以添加新添加的功能代码块
        '''
        return res
    return inner

def func1():
    print("hello")

func1 = wrapper(func1)
func1()

代码执行顺序:

  def为定义函数,不执行

  先执行同级代码,再执行下级函数体代码

 

 

二、装饰器语法糖

  装饰器的语法糖,是属于装饰器的(语法糖是装饰器内置的,可以引用所有的语法糖)

  @装饰器名字      装饰器的语法糖

  注意:在使用装饰器语法糖时,装饰器必须定义在被装饰对象之上

 

例子:统计函数执行时间

  不使用装饰器语法糖:

import time

# func函数执行三秒
def func():
    time.sleep(3)

# 装饰器:统计函数执行时间
def wrapper(func):    # func被装饰对象
    def inner(*args, **kwargs):    # *args, **kwargs是被装饰对象的参数
        # 调用前增加新功能
        start_time = time.time()
        res = func(*args, **kwargs)
        # 调用前增加新功能
        end_time = time.time()
        print(f"程序执行时间为:{end_time - start_time}秒")
        return res    # 调用被装饰对象,接收返回值
    return inner

# 不使用装饰器语法糖
func = wrapper(func)
func()

 

  使用装饰器语法糖:

import time

# 装饰器:统计函数执行时间
def wrapper(func):    # func被装饰对象
    def inner(*args, **kwargs):    # *args, **kwargs是被装饰对象的参数
        # 调用前增加新功能
        start_time = time.time()
        res = func(*args, **kwargs)
        # 调用前增加新功能
        end_time = time.time()
        print(f"程序执行时间为:{end_time - start_time}秒")
        return res    # 调用被装饰对象,接收返回值
    return inner

# 使用装饰器语法糖:使用装饰器语法糖时,装饰器必须定义在被装饰对象之上
@wrapper    # @wrapper    就等于--->   func = wrapper(func)
# func函数执行三秒
def func():
    time.sleep(3)



func()    # 因为语法糖可直接调用

 

 

三、装饰器练习题

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

  可写完对照

 

# 定义一个字典来做判断
user_info = {
    "user" : None,    # username与user_info["user"]来判断是否已登录用户
}

# 登录功能:
def login():
    # 判断用户没有登录时,执行
    # 登陆功能
    print("请先登录")
    username = input("请输入你的用户名:").strip()
    password = input("请输入您的密码:").strip()
    # 打开密码表,对比账户密码是否正确
    with open("dir/passwd.txt", "r", encoding="utf-8") as f:
        for line in f:
            # print(line)
            name, pwd = line.strip("\n").split(":")  # 得到[tank, 123]
    if username == name and password == pwd:
        print("登陆成功!")
        user_info["user"] = username
    else:
        print("登录失败")

# 登录认证装饰器:
def login_auth(func):
    def inner(*args, **kwargs):
        # 登录认证功能
        # 如果已经登录,将被装饰对象直接调用并返回
        if user_info.get("user"):
            res = func(*args, **kwargs)
            return res
        # 如果没有登录,执行登录功能
        else:
            login()    # 调用login()函数进行登录
    return inner

# func1、2、3都需要先登录才能使用,若登陆一次,后续就不需要再次登录
# 登陆之后可以使用的功能1
@login_auth
def func1():
    print("from func1")
    pass

# 登陆之后可以使用的功能2
@login_auth
def func2():
    print("from func2")
    pass

# 登陆之后可以使用的功能3
@login_auth
def func3():
    print("from func3")
    pass


# 执行环节
while True:
    func1()
    input("延迟操作")
    func2()
    func3()
装饰器练习题

 

 

四、叠加装饰器

1、什么是叠加装饰器

  在同一个被装饰对象中,添加多个装饰器并执行

 

2、为什么要使用叠加装饰器

  不使用叠加装饰器会导致代码冗余,结构不清晰,可扩展性差,所以最好每一个新的功能都应该写一个新的装饰器

 

3、怎么使用叠加装饰器

  使用方式:

 

    @装饰1

    @装饰2

    @装饰3

    def 被装饰对象():

      pass

 

例子1:

  我的需求:为被装饰对象,添加统计与登录认证功能

import time
user_info = {
    "user": None
}

# 登陆功能
def login():
    username = input("请输入您的账户:").strip()
    password = input("请输入您的密码:").strip()
    with open("dir/passwd.txt", "r", encoding="utf-8") as f:    # 我在同级目录下创建了一个dir目录,然后在目录下创建了一个passwd的文本文档,里面内容为tank:123,然后回车另起一行保存
        info = f.readline().strip("\n").split(":")
        name, pwd = info
        if username == name and password == pwd:
            print("登陆成功...")
            user_info["user"] = username
        else:
            print("登录失败...")

# 登录认证装饰器
def login_auth(func):
    def inner1(*args, **kwargs):
        # 登录认证功能
        if user_info.get("user"):
            res = func(*args, **kwargs)
            return res
        else:
            login()
            return func(*args, **kwargs)    # 无论inner1中出现任何判断,最后都要返回“调用后的被装饰对象”:func(*args, **kwargs)
    return inner1
##### 注意:登陆这里无论inner1中出现任何判断,最后都要返回“调用后的被装饰对象”:func(*args, **kwargs)

# 统计时间装饰器
def time_record(func):
    def inner2(*args, **kwargs):
        print("开始统计...")
        start_time = time.time()
        res = func(*args, **kwargs)
        end_time = time.time()
        print(f"消耗时间为{end_time - start_time}")
        return res
    return inner2

# 装饰顺序:先装饰time_record,再装饰login_auth
@login_auth    # inner1 = login_auth(inner2)
@time_record     # inner2 = time_record(download_movie)
def download_movie():
    print("电影开始下载...")
    time.sleep(3)
    print("电影下载完成!")
    return "gtwz.mp4"


# 执行顺序:先执行login_auth,再执行time_record
# 只统计下载电影的时间
login()    # 先调用用户登录,模拟用户已登录
download_movie()
装饰器例子1

注意:装饰器在调用被装饰对象时才会执行添加的功能

  装饰的顺序:由下到上

  执行的顺序:由上到下

 

  附上图解:

 

例子2:通过例子看到执行过程:

def wrapper1(func1):
    def inner1(*args, **kwargs):
        print("1-----start")
        # 被装饰对象在调用时,如果还有其他装饰器,会先执行其他装饰器中的inner
        # 下一步执行inner2
        res = func1(*args, **kwargs)
        print("1-----end")
        return res
    return inner1

def wrapper2(func2):
    def inner2(*args, **kwargs):
        print("2-----start")
        # 被装饰对象在调用时,如果还有其他装饰器,会先执行其他装饰器中的inner
        # 下一步执行inner3
        res = func2(*args, **kwargs)
        print("2-----end")
        return res
    return inner2

def wrapper3(func3):
    def inner3(*args, **kwargs):
        print("3-----start")
        # 被装饰对象在调用时,没有其他装饰器
        # 下一步执行index
        res = func3(*args, **kwargs)
        print("3-----end")
        return res
    return inner3


@wrapper1    # inner1 = wrapper1(inner2)
@wrapper2    # inner2 = wrapper2(inner3)
@wrapper3    # inner3 = wrapper3(index)
# 被装饰对象
def index():
    print("hello")


"""
装饰顺序:
    从下到上
执行顺序:
    从上到下
    
本例子的执行顺序:
    inner1()
    inner2()
    inner3()
    index()
    inner3的res
    inner2的res
    inner1的res
"""
装饰器例子2

 

 

五、无参装饰器和有参装饰器

1、无参装饰器

  在被装饰对象装饰时,没有传参数的装饰器


    @wrapper1
    @wrapper2
    @wrapper3


  无参装饰器模板:

# 无参装饰器模板:
def wrapper(func):
    def inner(*args, **kwargs):
        # 添加代码块功能
        res = func(*args, **kwargs)
        # 添加代码块功能
        return res
    return inner

 

2、有参装饰器

  有参装饰器:在某些时候,需要给用户的权限进行分类

    @wrapper1(参数1)
    @wrapper2(参数2)
    @wrapper3(参数3)

  有参装饰器模板:

# 有参装饰器模板:
def outer(para):    # 传入的para参数是用在inner()内部函数使用
    def wrapper(func):
        def inner(*args, **kwargs):
            if para == "":  # 功能1
                # 添加调用前的功能代码块1
                res = func(*args, **kwargs)
                # 添加调用后的功能代码块1
                return res
            elif para == "":  # 功能2
                # 添加调用前的功能代码块2
                res = func(*args, **kwargs)
                # 添加调用后的功能代码块2
                return res
            else:  # 功能3
                # 添加调用前的功能代码块3
                res = func(*args, **kwargs)
                # 添加调用后的功能代码块3
                return res
            # 无论inner中出现任何判断,最后都要返回“调用后的被装饰对象”:func(*args, **kwargs)
        return inner
    return wrapper

 

例1:可通过传参查看用户权限/功能

def user_auth(user_role):
    def wrapper(func):
        def inner(*args, **kwargs):
            if user_role == "SVIP":
                # 添加svip的权限/功能
                print(user_role)
                res = func(*args, **kwargs)
                return res
            elif user_role == "VIP":
                # 添加vip的权限/功能
                print(user_role)
                res = func(*args, **kwargs)
                return res
        return inner
    return wrapper


@user_auth("VIP")
def index():
    pass

index()

 

 

六、wraps修复(了解)

  wraps是一个修复工具,修复的是被装饰对象的空间

functools import wraps

def wrapper(func):
    @wraps(func)    # 修改名称空间:inner ---> func
    def inner(*args, **kwargs):
        """
        这里是装饰器
        :param args:
        :param kwargs:
        :return:
        """
        res = func(*args, **kwargs)
        return res
    return inner


def index():
    """
    这里是被装饰对象
    :return:
    """
    pass

print(index)
print(index.__doc__)
wraps使用示例
posted @ 2020-10-10 19:11  chchcharlie、  阅读(292)  评论(0编辑  收藏  举报