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)
- 首先,装饰器
@get_time
被执行,它将函数index
作为参数传递给装饰器函数get_time
。因为get_time
是一个装饰器函数,它会返回一个新的函数inner
来替换原来的函数index
。 - 接着,装饰器
@login_auth
被执行,它将函数inner
作为参数传递给装饰器函数login_auth
。因为login_auth
也是一个装饰器函数,它会返回一个新的函数auth
来替换原来的函数inner
。 - 现在,函数
index
已经被装饰成auth
函数,即index = login_auth(get_time(index))
。 - 当我们调用函数
index()
时,实际上是调用被装饰后的auth
函数。auth
函数首先执行登录验证,验证通过后再执行原始函数inner
。 - 函数
inner
中,先记录下函数执行之前的时间,然后再执行原始函数index
(即被装饰的函数)。在执行完原始函数后,再记录下函数执行之后的时间,并计算出函数实际执行的时间。 - 最后,函数
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()函数执行
执行顺序的理解:
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’
@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()
要求:
-
有注册、登录功能,所有数据存在文件中
-
添加员工、查看员工需要管理员权限
-
封装成函数并使用装饰器,接收参数
# 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, "产品经理")