python 装饰器

一、核心思想

不改变被装饰对象内部代码原有调用方式的基础之上在添加额外的功能

二、装饰器的实现过程

根据实际需要,一步一步满足需求,完成对装饰器的理解

1、简易版本

给index函数添加统计执行时间的功能

import time

def index():
    time.sleep(3)
    print('from index')

def home():
    time.sleep(2)
    print('from home')

def func():
    time.sleep(2)
    print('from home')

def outer(func): # 外层函数
    def get_time(): # 内层函数
 
        # 1. 在函数执行之前打一个时间点
        start_time = time.time()
        
        func()  # index() 

        # 2. 在函数执行完毕之后在打一个时间点
        end_time = time.time()

        # 3. 两个时间的差值就是函数的实际执行时间
        print("函数执行了:%s秒" % (end_time - start_time))

    return get_time  # 返回内层函数


# 利用闭包的形式来传参
# res = outer(index)  # res:get_time的内存地址
# res()  # get_time()

home = outer(home)
home()

2、解决参数的问题

当执行不同的函数,计算不同函数的执行时间,有的函数有参数,有的函数没参数

def index(name, username):
    time.sleep(3)
    print('from index')

def home(name):
    time.sleep(2)
    print('from home', name)

def outer(func):
    def get_time(*args, **kwargs):
        
        # 1. 在函数执行之前打一个时间点
        start_time = time.time()
        
        func(*args, **kwargs)  # 原有函数
        
        # 2. 在函数执行完毕之后在打一个时间点
        end_time = time.time()

        # 3. 两个时间的差值就是函数的实际执行时间
        print("函数执行了:%s秒" % (end_time - start_time))

    return get_time
index = outer(index)
index('tony', username='tank')            

3、解决返回值问题

def home(name):
    time.sleep(2)
    print('from home', name)

def index():
    time.sleep(3)
    print('from index')
    return 'from index'

def outer(func):
    # func = index
    def get_time(*args, **kwargs):
        # func:index
        # 1. 在函数执行之前打一个时间点
        start_time = time.time()
        res=func(*args, **kwargs)  # index()  func('tony')
        # 2. 在函数执行完毕之后在打一个时间点
        end_time = time.time()

        # 3. 两个时间的差值就是函数的实际执行时间
        print("函数执行了:%s秒" % (end_time - start_time))
        return res
    return get_time
index = outer(index)
res=index()
print(res)

4、装饰器模板

def outer(func):
    def inner(*args, **kwargs):
        print('这个是函数执行之前可以添加的功能')
        res = func(*args, **kwargs)
        print('这个是函数执行之后可以添加的功能')
        return res

    return inner


def index():
    print('from index')


index = outer(index)
index()

5、装饰器的语法糖

'''
只要有一个函数认证成功,其他函数就不用验证了
需要一个变量来保存登录用户登录成功的标志
'''
is_login = {'is_login': False}

# 使用闭包函数实现装饰器
def login(func):
    def auth(*args, **kwargs):  # 可变长参数:接受多余的位置参数和关键字参数,没有参数则接受为空,不报错,正常执行,满足有参和无参两种使用场景
        if is_login.get('is_login'):  # 字典 get 方法取值为 False,表示 if False:条件不成立时不执行
            # 前一个函数执行完,is_login赋值为 True,执行下面的代码
            res = func(*args, **kwargs)  # 调用函数,函数的返回值赋值给 res,再返回 res 给到 auth 函数
            return res
            # 简易登录认证系统
        username = input("Enter username: ").strip()
        passwd = input("Enter password: ").strip()
        # 验证用户名密码
        if username == 'jingzhiz' and passwd == '123':
            print('登录成功!')  # 函数执行前的操作
            res = func(*args, **kwargs)  # 要执行的函数
            is_login['is_login'] = True
            return res  # 函数执行后的操作
        else:
            print('认证失败!')

    return auth

'''
 语法糖需要定义在装饰器的下方,函数的上方紧贴着
 去掉语法糖可以实现原函数的正常调用
 语法糖格式@+装饰器外层函数名
 @login的理解 
   index=login(),index 只是一个变量名,也可以是res,只是为了满足装饰器思想:不改变原函数的调用方式写成原函数名,打印内存地址于原函数不是一个
   login()会返回 auth 的(函数名)即内存地址,实现 index = auth 即 index()=auth()。
'''
@login
def index(name):
    print('from index', name)
    return '返回值测试'

@login
def home():
    print('from home')

# 装饰器的最终效果:保持函数的原始调用方式的同时,增加新的功能
res = index('jinghiz')
print(res)
home()

6、装饰器修复技术

functools.wraps函数可以拷贝原函数的属性信息。warps源码中通过使用partial函数复制了原函数的信息。

使用修复技术的效果:函数 view_all_employees 运行时间:0.0018458366394042969 秒"。

未使用:函数 wrapper 运行时间:0.0018458366394042969 秒"。

import time
import functools

def time_count(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        run_time = end_time - start_time
        print(f"函数 {func.__name__} 运行时间:{run_time} 秒")
        return result

    return wrapper

def admin_required(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        global current_user
        if current_user['role'] == 'admin':
            return func(*args, **kwargs)
        else:
            print("只有管理员才能执行此操作!")
            return None

    return wrapper

@time_count
@admin_required
def view_all_employees():
    with open('employee_data_file', "r") as f:
        print("所有员工信息:")
        for line in f:
            employee_data = line.strip().split(",")
            print(f"员工姓名:{employee_data[0]},年龄:{employee_data[1]},职位:{employee_data[2]}")

view_all_employees()  # 执行被装饰的函数

7、双层语法糖

使用场景:将登录验证、计算函数执行时间两个功能附加给一个原函数

# 双层语法糖
import time

# 登录验证功能
def login_auth(func):
    # func = index
    def auth(*args, **kwargs):
        # 写登录功能
        # 1.
        username = input('username:').strip()
        password = input('password:').strip()

        # 2. 验证用户名和密码是否正确
        if username == 'kevin' and password == '123':
            print('登录成功')
            res = func(*args, **kwargs)
            return res
        else:
            print('用户名或者密码错误')
    return auth

# 计算函数执行时间
def get_time(func):
    # func = index
    def inner(*args, **kwargs):
        # func:index
        # 1. 在函数执行之前打一个时间点
        start_time = time.time()
        res = func(*args, **kwargs)  # index()  func('tony')
        # 2. 在函数执行完毕之后在打一个时间点
        end_time = time.time()

        # 3. 两个时间的差值就是函数的实际执行时间
        print("函数执行了:%s秒" % (end_time - start_time))
        return res
    return inner

@login_auth  # index=login_auth(get_time内部的inner函数名)
@get_time  # get_time内部的inner函数名,等价于get_time(index)
def index():
    time.sleep(2)
    print('from index')

index()  # auth()

从下往上执行,最外层语法糖使用下面的函数名,即index = login_auth(inner)

下面是代码的执行顺序:
  1. 首先,装饰器 @get_time 被执行,它将函数 index 作为参数传递给装饰器函数 get_time。因为 get_time 是一个装饰器函数,它会返回一个新的函数 inner 来替换原来的函数 index
  2. 接着,装饰器 @login_auth 被执行,它将函数 inner 作为参数传递给装饰器函数 login_auth。因为 login_auth 也是一个装饰器函数,它会返回一个新的函数 auth 来替换原来的函数 inner
  3. 现在,函数 index 已经被装饰成 auth 函数,即 index = login_auth(get_time(index))
  4. 当我们调用函数 index() 时,实际上是调用被装饰后的 auth 函数。auth 函数首先执行登录验证,验证通过后再执行原始函数 inner
  5. 函数 inner 中,先记录下函数执行之前的时间,然后再执行原始函数 index(即被装饰的函数)。在执行完原始函数后,再记录下函数执行之后的时间,并计算出函数实际执行的时间。
  6. 最后,函数 auth 返回原始函数 inner 的返回值,即函数 index 的返回值。
因此,整个过程中,函数 index 实际上被双重装饰了,首先是被装饰成计算函数执行时间的函数 inner,然后再被装饰成登录验证的函数 auth。这种双重装饰的方式可以让多个装饰器按照特定顺序依次作用于同一个函数上,从而实现更加复杂的功能。

8、三层语法糖

def outter1(func1):
    print('加载了outter1')
    def wrapper1(*args, **kwargs):
        print('执行了wrapper1')
        res1 = func1(*args, **kwargs)
        return res1

    return wrapper1

def outter2(func2):
    print('加载了outter2')

    def wrapper2(*args, **kwargs):
        print('执行了wrapper2')
        res2 = func2(*args, **kwargs)
        return res2

    return wrapper2

def outter3(func3):
    print('加载了outter3')

    def wrapper3(*args, **kwargs):
        print('执行了wrapper3')
        res3 = func3(*args, **kwargs)
        return res3

    return wrapper3

@outter1  # index =  outter1(wrapper2) = wrapper1
@outter2  # wrapper2 = outter2(wrapper3)
@outter3  # wrapper3 = outter3(index)
def index():
    print('from index')

index()  # wrapper1 被index 调用。index() 函数执行等于 wrapper1()函数执行

执行顺序的理解:

1、outter3 首选会被执行 ------> 加载了outter3 -------> 返回 wrapper3 给到 outter2 作为 func2 传参

2、outter2 接着会被执行------> 加载了outter2 -------> 返回 wrapper2 给到 outter1 作为 func2 传参

3、outter1 接着会被执行------> 加载了outter1 -------> 返回 wrapper1 给到 index ,此时 index = wrapper1

4、index()调用相当于wrapper1(), 此时调用wrapper1()-------> 执行了wrapper1----->func1为wrapper2,

wrapper2()被调用 ---------------------------------->执行了wrapper2----->func2为wrapper3,

wrapper3()被调用---------------------------------->执行了wrapper3----->func3为index 

index()被调用---------------------------------->‘from index’

执行结果与过程过程分析

9、有参装饰器

有参装饰器与无参装饰器对比:

1、最外层多了一层函数的嵌套,这一层带上形参。

  第二层和第三层和之前一样,接受函数名参数和变量

2、语法糖部分:带上实参

  无参情况,@outter就行。有参数@outter('实参')

# 有参装饰器就是带参数的装饰器
# 先前我们学习的装饰器就是无参装饰器:不到参数的装饰器

def outter(type):
    def login_auth(func):
        def auth(*args, **kwargs):
            # 写登录功能
            # 1.接收用户输入账号密码
            username = input('username:').strip()
            password = input('password:').strip()

            # 2. 验证用户名和密码是否正确
            # with open('')
            """
                根据不同的参数用户名和密码来自于不同的位置
            """
            if type == 'file':
                print('用户名和密码来自于文件')
            elif type == 'mysql':
                print('用户名和密码来自于MySQL')
            elif type == 'oracle':
                print('用户名和密码来自于Oracle')

            # if username == 'kevin' and password == '123':
            #     print('登录成功')
            #     res = func(*args, **kwargs)
            #     return res
            # else:
            #     print('用户名或者密码错误')
        return auth
    return login_auth

# 语法糖处添加带上参数,无参装饰器这里不用带()
@outter('file')  # @login_auth
# 如果是index函数认证,用户名和密码来自于文件  login_auth(index, 1)
def index():
    print('from index')


@outter('mysql')
# 如果是home函数认证,用户名和密码来MySQL
def home():
    print('from home')


@outter('oracle')
# 如果是func函数认证,用户名和密码来oracle
def func():
    print('from func')


# 调用函数,原有的调用方式不变
index()
home()
func() 

10、函数版的员工管理系统

要求:

  1. 有注册、登录功能,所有数据存在文件中

  2. 添加员工、查看员工需要管理员权限

  1. 封装成函数并使用装饰器,接收参数

# staff直译为工作人员,且是个统称,一般用复数形式staffs。
# employee直译为雇员的意思,一般指合同工

# 全局变量
login_status = False  # 登录状态
current_user = None  # 当前的用户
user_data_file = "user_data.txt"  # 存登录系统用的账号密码
employee_data_file = "employee_data.txt"  # 员工信息

# 管理员认证功能
def admin_required(func):
    def wrapper(*args, **kwargs):
        global current_user
        if current_user['role'] == "admin":
            return func(*args, **kwargs)
        else:
            print("只有管理员才能执行此操作!")
            return None

    return wrapper

# 注册功能
def register(username, password, role):
    with open(user_data_file, "r", encoding='utf-8') as f:
        res = f.readlines()
        for zh in res:
            if username == zh.split(',')[0]:
                print('用户已经注册过了!')
                break
        else:
            with open(user_data_file, "a", encoding='utf-8') as f:
                f.write(f"{username},{password},{role}\n")
                print(f"用户 {username} 已成功注册!")

# 登录功能
def login(username, password):
    global login_status, current_user
    with open(user_data_file, "r", encoding="utf-8") as f:
        for line in f:
            user_data = line.strip().split(",")
            if username == user_data[0] and password == user_data[1]:
                login_status = True
                current_user = {"username": user_data[0], "role": user_data[2]}
                print(f"欢迎 {current_user['username']} 登录!")
                return
    print("登录失败,请检查用户名和密码!")

# 添加员工信息
@admin_required
def add_employee(name, age, position):
    with open(employee_data_file, "r", encoding="utf-8") as f:
        res = f.readlines()
        for user_info in res:
            if name == user_info.split(',')[0]:
                print(f'{name}已经添加过了!')
                break
        else:
            with open(employee_data_file, "a", encoding="utf-8") as f:
                f.write(f"{name},{age},{position}\n")
                print(f"已成功添加员工 {name}!")

# 查看指定的员工
@admin_required
def view_employee(name):
    with open(employee_data_file, "r") as f:
        for line in f:
            employee_data = line.strip().split(",")
            if name == employee_data[0]:
                print(f"员工姓名:{employee_data[0]},年龄:{employee_data[1]},职位:{employee_data[2]}")
                return
    print("未找到指定员工!")

# 查看所有员工
@admin_required
def view_all_employees():
    with open(employee_data_file, "r") as f:
        print("所有员工信息:")
        for line in f:
            employee_data = line.strip().split(",")
            print(f"员工姓名:{employee_data[0]},年龄:{employee_data[1]},职位:{employee_data[2]}")

if __name__ == "__main__":
    # # 模拟用户注册、登录和执行操作的过程
    register("admin", "admin123", "admin")
    register("jingzhiz", "user123", "common")

    # 只有在添加、查看功能之前添加login功能才能将全局变量修改,登录状态改为 True、当前用户去文本文件读取
    # login_status = True
    # current_user = {"username": user_data[0], "role": user_data[2]}
    login("jingzhiz", "jingzhiz123")

    # # 添加员工
    add_employee("张三2", 26, "工程师")
    add_employee("李四2", 22, "设计师")

    # # 查看员工
    view_employee('张三1')
    view_all_employees()
    #
    login("user", "user123")
    add_employee("王五", 30, "产品经理")

 

posted @ 2023-05-30 16:01  凡人半睁眼  阅读(58)  评论(0编辑  收藏  举报