Python高级之装饰器

【一】装饰器

【1】什么是装饰器

  • 装饰代指为被装饰对象添加新的功能,器代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。
  • 装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能

【2】装饰器的用途

  • 软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃
  • 而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器

【3】装饰器的分类

  • 分为有参装饰器和无参装饰器
  • 原理都是闭包函数+函数对象的组合使用

【二】无参装饰器

【1】引入

  • 添加计算执行时间的功能
import time


def timer():
    time.sleep(2)
    print("你别睡过头了")


timer()


import time

【2】直接计时

import time


def timer():
    time.sleep(2)


start_time = time.time()
timer()
end_time = time.time()
print(f"你已经睡了{end_time - start_time} 秒")
# 你已经睡了2.008584976196289 秒

【3】函数作为参数

import time


def timer():
    time.sleep(2)


def outer(func):
    start_time = time.time()
    func()
    end_time = time.time()
    print(f"你已经睡了{end_time - start_time} 秒")
    return func


outer(timer)
# 你已经睡了2.008584976196289 秒

【4】闭包函数

import time


def timer():
    time.sleep(2)


def outer(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


inner = outer(timer)
inner()
# 你已经睡了2.008584976196289 秒
#这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能

【5】被装饰的函数带参数会报错

import time


def timer(func):
    time.sleep(2)


def outer(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


inner = outer(timer)
inner()
'''
Traceback (most recent call last):
  File "D:\Python\pythonProject\pythonProject1\demo7.py", line 659, in <module>
    inner()
  File "D:\Python\pythonProject\pythonProject1\demo7.py", line 651, in inner
    func()
TypeError: timer() missing 1 required positional argument: 'func'
'''

【6】无参装饰器模版

# 通过参数 func 接收外部的函数地址
def outer(func):
    def inner():
        # 第一部分:执行外部传入的函数之前执行的代码
        '''...'''
        # 第二部分:执行外部传入的函数地址
        func()
        # 第三部分:执行外部传入的函数之后执行的代码
        '''...'''
    return inner

【三】有参装饰器

【1】有参装饰器

import time


def timer(name):
    time.sleep(2)
    print(f"{name}已经进入睡觉时间")


def outer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner

# 调用外层函数outer,返回了内层函数inner的地址
inner = outer(timer)
inner('ligo')
# ligo已经进入睡觉时间
# 你已经睡了2.0065224170684814 秒

【2】有参装饰器模版

# 通过参数 func接收外部的函数地址
def outer(func):
    def inner(*args, **kwargs):
        # 第一部分:执行外部传入的函数之前执行的代码
        '''...'''
        # 第二部分:执行外部传入的函数地址
        func(*args, **kwargs)
        # 第三部分:执行外部传入的函数之后执行的代码
        '''...'''
    return inner

【四】无参语法糖

【1】无装饰器参语法糖

  • 为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式
  • 需要在被装饰对象的正上方单独一行添加@timer
  • 当解释器解释到 @timer时就会调用timer函数
  • 且把它正下方的函数名当做实参传入
  • 然后将返回的结果重新赋值给原函数名
#【1】无语法糖
import time


def outer(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


def timer():
    time.sleep(2)


timer = outer(timer)
timer()

#【2】有语法糖
import time


def outer(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


@outer
def timer():
    time.sleep(2)


timer()

【2】有装饰器参语法糖

import time


def outer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


@outer
def timer(name):
    time.sleep(2)
    print(f"{name}已经进入睡觉时间")


timer('ligo')

【五】有参语法糖

【1】单语法糖

import time


user_data = {'username': 'ligo'}


def check_login(func):
    def inner(*args, **kwargs):
        if user_data.get('username'):
            func(*args, **kwargs)
        else:
            print("你需要先登陆!")
    return inner


def outer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


@check_login
def timer(name):
    time.sleep(2)
    print(f"{name}已经进入睡觉时间")


timer('ligo')

【2】多语法糖

import time

user_data = {'username': 'ligo'}


def check(tag):
    if tag == 'check_login':
        def check_login(func):
            def inner(*args, **kwargs):
                if user_data.get('username'):
                    func(*args, **kwargs)
                else:
                    print("你需要先登陆!")

            return inner

        return check_login
    elif tag == 'outer':
        def outer(func):
            def inner(*args, **kwargs):
                start_time = time.time()
                func(*args, **kwargs)
                end_time = time.time()
                print(f"你已经睡了{end_time - start_time} 秒")

            return inner

        return outer


@check(tag='check_login')
@check(tag='outer')
def timer(name):
    time.sleep(2)
    print(f"{name}已经进入睡觉时间")


timer('ligo')

【3】多语法糖案例

#【1】原始方法
user_data = {'username': "dream", "password": "521"}


def check_username(func):
    # 【六】进入到 check_username 里面
    # func = check_password 里面的inner
    print('check_username')

    # 【十】调用inner
    def inner(*args, **kwargs):
        # 【十一】进来函数内容
        print('校验用户名之前')
        username = input("username :>>>> ").strip()
        if user_data.get("username") != username:
            print(f"用户名错误!")
        # 【十二】调用 func 函数 check_password 里面的inner
        func(*args, **kwargs)
        # 【十七】执行完  check_password 里面的inner 回来
        print('校验用户名之后')

    # 【七】返回inner
    return inner


def check_password(func):
    # 【二】调用 check_password 进来
    print("check_password")

    # 【十三】来到 check_password 里面的 inner 是从 check_username 的inner来的
    def inner(*args, **kwargs):
        # 【十四】进入到函数内部
        print('校验密码之前')
        password = input("password :>>>> ").strip()
        if user_data.get("password") != password:
            print(f"密码错误!")
        # 【十五】进入到func函数内部 -- 真正的 transform
        func(*args, **kwargs)
        # 【十六】走完 transform 回来继续走
        print('校验密码之后')

    # 【三】返回 inner
    return inner


def transform():
    print("这是主函数的 transform")


# 原来的方法 ---> 校验用户名和密码
# 先校验用户名  后校验密码

# 【一】进入到 check_password 函数里面 ---> 获取到返回值 这个返回值就是 check_password 藜麦你的inner
# 【四】transform 就是返回的inner 的函数内存地址
transform = check_password(transform)  # 在 check_password 里面的 inner 里面的 func 是我上面的 transform 函数
# 【五】进入到 check_username 函数里面
# 【八】transform 就是 check_username 里面返回的inner
transform = check_username(transform)  # transform 是因为我要先校验用户名 , 必须先触发 check_username 里面的 inner
# 【九】调用 inner
transform()
# 【十九】结束

# check_password
# check_username
# 校验用户名之前
# 校验密码之前
# 这是主函数的 transform
# 校验密码之后
# 校验用户名之后

#【2】使用语法糖
user_data = {'username': "dream", "password": "521"}


def check_username(func):
    # 【六】进入到 check_username 里面
    # func = check_password 里面的inner
    print('check_username')

    # 【十】调用inner
    def inner(*args, **kwargs):
        # 【十一】进来函数内容
        print('校验用户名之前')
        username = input("username :>>>> ").strip()
        if user_data.get("username") != username:
            print(f"用户名错误!")
        # 【十二】调用 func 函数 check_password 里面的inner
        func(*args, **kwargs)
        # 【十七】执行完  check_password 里面的inner 回来
        print('校验用户名之后')

    # 【七】返回inner
    return inner


def check_password(func):
    # 【二】调用 check_password 进来
    print("check_password")

    # 【十三】来到 check_password 里面的 inner 是从 check_username 的inner来的
    def inner(*args, **kwargs):
        # 【十四】进入到函数内部
        print('校验密码之前')
        password = input("password :>>>> ").strip()
        if user_data.get("password") != password:
            print(f"密码错误!")
        # 【十五】进入到func函数内部 -- 真正的 transform
        func(*args, **kwargs)
        # 【十六】走完 transform 回来继续走
        print('校验密码之后')

    # 【三】返回 inner
    return inner


# 装饰器语法糖谁在最上面就先校验谁
@check_username  # transform = check_username(transform) = check_username(check_password(transform))
@check_password  # transform = check_password(transform)
def transform():
    print("这是主函数的 transform")


# 原来的方法 ---> 校验用户名和密码
# 先校验用户名  后校验密码

# 【一】进入到 check_password 函数里面 ---> 获取到返回值 这个返回值就是 check_password 藜麦你的inner
# 【四】transform 就是返回的inner 的函数内存地址
# transform = check_password(transform)  # 在 check_password 里面的 inner 里面的 func 是我上面的 transform 函数
# 【五】进入到 check_username 函数里面
# 【八】transform 就是 check_username 里面返回的inner
# transform = check_username(transform)  # transform 是因为我要先校验用户名 , 必须先触发 check_username 里面的 inner
# 【九】调用 inner
transform()
# 【十九】结束

# check_password
# check_username
# 校验用户名之前
# 校验密码之前
# 这是主函数的 transform
# 校验密码之后
# 校验用户名之后

【六】综合练习

【1】需求

# 定义一个数据字典存储用户数据
# 分别校验用户名和密码
# 分别是两个装饰器
# 一个负责校验用户名是否存在
# 只有符合上一个装饰器的条件才能走到下一个装饰器

【2】套两层

user_data = {'username': 'ligo', 'password': '123'}


def get_username(func):
    def inner(*args, **kwargs):

        username_input = input("请输入用户名:").strip()
        if username_input == user_data.get('username'):
            print('用户名正确')
            func(*args, **kwargs)
        else:
            print('用户名错误')
    return inner


def get_password(func):
    def inner(*args, **kwargs):
        password_input = input("请输入密码:").strip()
        if password_input == user_data.get('password'):
            print('密码正确')
            func(*args, **kwargs)
        else:
            print('密码错误')
    return inner


def transform(username, money):
    print(f"{username}转账{money}")


transform = get_password(transform)
# password 的 inner
transform = get_username(transform)
# username 的 inner
transform('ligo', 1000)

【3】双层语法糖

user_data = {'username': 'ligo', 'password': '123'}


def get_username(func):
    def inner(*args, **kwargs):

        username_input = input("请输入用户名:").strip()
        if username_input == user_data.get('username'):
            print('用户名正确')
            func(*args, **kwargs)
        else:
            print('用户名错误')
    return inner


def get_password(func):
    def inner(*args, **kwargs):
        password_input = input("请输入密码:").strip()
        if password_input == user_data.get('password'):
            print('密码正确')
            func(*args, **kwargs)
        else:
            print('密码错误')
    return inner


@get_username
@get_password
def transform(username, money):
    print(f"{username}转账{money}")


transform('ligo', 1000)

【4】双层有参语法糖

user_data = {'username': 'ligo', 'password': '123'}


def check_user(tag):
    if tag == 'get_username':
        def get_username(func):
            def inner(*args, **kwargs):
                username_input = input("请输入用户名:").strip()
                if username_input == user_data.get('username'):
                    print('用户名正确')
                    func(*args, **kwargs)
                else:
                    print('用户名错误')

            return inner

        return get_username
    elif tag == 'get_password':
        def get_password(func):
            def inner(*args, **kwargs):
                password_input = input("请输入密码:").strip()
                if password_input == user_data.get('password'):
                    print('密码正确')
                    func(*args, **kwargs)
                else:
                    print('密码错误')

            return inner

        return get_password


@check_user(tag='get_username')
@check_user(tag='get_password')
def transform(username, money):
    print(f"{username}转账{money}")


transform('ligo', 1000)

【七】伪装装饰器

【1】查看装饰器(help)

  • 可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性
import time


def outer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


@outer
def timer(name):
    """
      timer page function
      :param name: str
      :return: None
    """
    time.sleep(2)
    print(f"{name}已经进入睡觉时间")


print(help(timer))
'''
Help on function inner in module __main__:

inner(*args, **kwargs)

None
'''

【2】伪装装饰器(functools)

  • functools模块下提供一个装饰器wraps专门来帮助我们实现保留原函数属性
import time
from functools import wraps


def outer(func):
    @wraps(func)
    def inner(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(f"你已经睡了{end_time - start_time} 秒")

    return inner


@outer
def timer(name):
    """
      timer page function
      :param name: str
      :return: None
    """
    time.sleep(2)
    print(f"{name}已经进入睡觉时间")


print(help(timer))
'''
Help on function timer in module __main__:

timer(name)
    timer page function
    :param name: str
    :return: None

None
'''
posted @ 2024-05-09 15:09  Ligo6  阅读(52)  评论(0)    收藏  举报