装饰器规范:
# 装饰器作用:不改变原始函数的代码,但需要在原始函数的开始和结束添加新的功能,并执行函数。
# 开放封闭原则:对原始函数代码保持封闭不可修改,对原始代码的扩展功能保持开放
# 装饰器的构建逻辑:了解被装饰的函数,定义装饰器,定义内部函数,返回内部函数的结果值,返回内部函数,语法糖,调用装饰器
# 装饰器的运行逻辑:闭包(函数内部引用函数外部变量或函数),返回函数对象,装饰器函数
# 重构函数:修改原始函数内部的代码,容易导致其他调用该函数的模块,全部失效
# 语法糖:@装饰器函数名,放在被装饰的函数头上,作用等同于“func = timmer(func)”,表示给不改变原始函数的情况下,给原始函数新增功能
# 装饰器的参数:被装饰的函数如果增加参数,装饰器函数内部inner和func都需要新增不定长万能参数
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
'''
Author:leo
Date&Time:2021/6/21 15:52
Project:python
FileName:20210621_装饰器.py
Desc:装饰器
书籍推荐:python核心编程(第三版)
1.基础装饰器
2.参数装饰器
3.多级装饰器
'''
# --基础装饰器--
print("\n" + " 1.0 基础装饰器(开始)".center(100, "*"))
'''
全栈开发(一):P52
# 装饰器作用:不改变原始函数的代码,但需要在原始函数的开始和结束添加新的功能,并执行函数。
# 开放封闭原则:对原始函数代码保持封闭不可修改,对原始代码的扩展功能保持开放
# 装饰器的构建逻辑:了解被装饰的函数,定义装饰器,定义内部函数,返回内部函数的结果值,返回内部函数,语法糖,调用装饰器
# 装饰器的运行逻辑:闭包(函数内部引用函数外部变量或函数),返回函数对象,装饰器函数
# 重构函数:修改原始函数内部的代码,容易导致其他调用该函数的模块,全部失效
# 语法糖:@装饰器函数名,放在被装饰的函数头上,作用等同于“func = timmer(func)”,表示给不改变原始函数的情况下,给原始函数新增功能
# 装饰器的参数:被装饰的函数如果增加参数,装饰器函数内部inner和func都需要新增不定长万能参数
'''
import time
from functools import wraps
def wrapper(func): # 装饰器函数, f表示被装饰的函数
'''
装饰器函数的注释内容:案例-计算函数的执行时间
:param func: 被装饰的函数对象
:return: 返回内部函数对象
'''
@wraps(func) # 系统自带的装饰器,加上以后,可以通过__name__和__doc__调用原始的装饰器名称和注释
def inner(*args, **kwargs): # 内部函数
'''
装饰器内部函数
:param args: 元组(1,2,3,4)
:param kwargs: 字段(name=“leo”,age=18)
:return:返回被装饰的函数的返回值
'''
print("1.新增被装饰的函数执行之前,需要做的事情的代码")
start_time = time.time()
func_return = func(*args, **kwargs) # 被装饰的函数
print("3.新增被装饰的函数执行之后,需要做的事情的代码")
end_time = time.time()
print("4.执行装饰器的内部函数:", inner.__name__, start_time-end_time)
return func_return # 返回被装饰的函数的返回值
return inner # 装饰器返回的内部函数名称,通过内部函数对象来调用,避免fun(fun)导致代码逻辑错乱
# 语法糖:@装饰器函数名,放在被装饰的函数头上,作用等同于“func = timmer(func)”,表示给不改变原始函数的情况下,给原始函数新增功能
@wrapper # func = wrapper(func),表示把下面的函数以参数的形式传给装饰器,并重新执行新的下面的函数
def func(arg_str):
'''
被装饰的函数的注释内容:案例-计算函数的执行时间
:param arg_str:字符串
:return: 返回原始函数执行的结果值
'''
time.sleep(0.01)
print(f"2.func:执行带单个参数的被装饰的函数,返回传入的参数:" + arg_str)
print(f"2-1.func:被装饰器装饰过的函数,装饰器内部加上@wraps(func),返回原始函数名称,不加,则返回装饰器内部的名称innner:" + str(func.__name__))
print(f"2-2.func:被装饰器装饰过的函数,装饰器内部加上@wraps(func),返回原始函数注释,不加,则返回装饰器内部的注释inner:" + str(func.__doc__))
return f'5.func:装饰器的执行结果'
@wrapper
def func_1(arg_str1, arg_str2):
time.sleep(0.01)
print("func_1:执行带多个参数的装饰的函数:", arg_str1, arg_str2)
return 'func_1:装饰器的执行结果'
# func = timmer(func) # 被@timmer代替
ret = func(arg_str="100") # 等同于:inner_1 = inner("100")
print(ret)
print("基础装饰器(结束)".center(100, "*"))
# --参数装饰器--
print("\n" + " 2.0 参数装饰器(开始)".center(100, "*"))
'''
问题:当给大量函数都加了装饰器,如果要取消装饰器,怎么操作?
解决:
1.正常的解决方案是一个一个注释装饰器
2.优化的解决方案是加一个全局变量,来控制装饰器的启用和弃用
3.最佳的解决方案是装饰器外部再包一层装饰器用来接受装饰器的参数,三层装饰器封装后就可以用参数来控制装饰器的启用和弃用(函数规范中最高内嵌也建议是三层,超过三层容易引起逻辑混乱)
'''
Flag = True # 通过变量Flag为真,则所有调用当前装饰器的函数都执行新函数代码;如果为假,则执行原始函数代码
# Flag = False
def timmer_out(flag): # 形参,名称可以随便取
def timmer(func):
def inner(*args, **kwargs):
if flag: # 用变量控制装饰器的执行,如果True,则执行装饰器;如果False,则不执行装饰器
start_time = time.time()
res = func(*args, **kwargs)
end_time = time.time()
print(f"执行装饰器:{start_time-end_time}")
return res
else: # 如果False,则不执行装饰器
res = func(*args, *kwargs)
return res
return inner
return timmer
# 执行顺序一般从右向左看,依次表示:
# 1.函数time_out+参数flag=装饰器最外层返回的对象timmer;
# 2.timmer加上@后,变成@timmer,等价于run_func=timmer(run_func)
@timmer_out(Flag) # 等价于:timmer = timmer_out(Flag)
def run_func():
time.sleep(0.01)
print("执行被装饰的函数:" + run_func.__name__)
return run_func.__name__
run_func()
print("参数装饰器(结束)".center(100, "*"))
# --多层装饰器--
print("\n" + " 3.0 多层装饰器(开始)".center(100, "*"))
'''
问题:如果又需要记录用户登录状态,又需要记录函数执行时间,就需要用到多层装饰器
重点:执行顺序是从被装饰的函数最近的wrapper_2到最远的wrapper_1,执行逻辑是从函数的外部到内部,再从内部到外部
'''
def wrapper_1(function): # function --> inner_2
def inner_1(*args, **kwargs):
print("开始执行装饰器1:" + inner_1.__name__)
res = function(*args, **kwargs) # function -- inner_2
print("结束执行装饰器1" + inner_1.__name__)
return res
return inner_1
def wrapper_2(function): # function -- > func
def inner_2(*args, **kwargs):
print("开始执行装饰器2:" + inner_2.__name__)
res = function(*args, **kwargs) # function - func
print("结束执行装饰器2" + inner_2.__name__)
return res
return inner_2
'''
多层装饰器执行顺序:第一个开始(外层)、第二个开始(内层)、被装饰的函数、第二个结束(内层)、第一个结束(外层)
执行结果:
开始执行装饰器1:inner_1
开始执行装饰器2:inner_2
被装饰的函数:func
结束执行装饰器2inner_2
结束执行装饰器1inner_1
'''
@wrapper_1 # 执行顺序2:func = wrapper_1(func) -- > wrapper_1(inner_2) = inner_1 -- > func = inner_1
@wrapper_2 # 执行顺序1:func = wrapper_2(func) -- > wrapper_2(func) = inner_2 -- > func = inner_2
def func():
print("被装饰的函数:func")
return "被装饰的函数返回值:func"
func()
print("多层装饰器(结束)".center(100, "*"))
class Test():
def __init__(self):
self.login_status = False
self.login_count = 1
def test_temp_1(self):
'''
题目1:编写一个装饰器,为多个函数添加认证功能(用户的账户密码来源于文件)
要求:用户登录一次,后续的函数无需再次输入用户名和密码
:return:
'''
# --装饰器案例-1--
print("\n" + "装饰器案例-1.登录(开始)".center(100, "*"))
def login(func):
def inner(*args, **kwargs):
# global login_status, login_count
while self.login_count < 4:
if self.login_status:
ret = func(*args, **kwargs)
return ret
else:
username = input(f"请输入姓名({self.login_count}):")
password = input(f"请输入密码({self.login_count}):")
if username == "admin" and password == "123456":
login_status = True
ret = func(*args, **kwargs)
return ret
else:
print("登录失败!")
self.login_count += 1
return inner
@login
def shoplist_add_1():
print("添加一个商品")
@login
def shoplist_del_1():
print("删除一个商品")
shoplist_add_1()
shoplist_del_1()
print("装饰器案例-1.登录(结束)".center(100, "*"))
def test_temp_2(self):
'''
题目2:编写一个装饰器,为多个函数添加记录日志功能,要求每次调用函数,都将函数名称写入文件
:return:
'''
# --装饰器案例-2--
print("\n" + "装饰器案例-2.日志(开始)".center(100, "*"))
def log(func):
def inner(*args, **kwargs):
with open("func_log.txt", "a", encoding="utf-8") as log_file:
log_file.write(f"函数名称:{func.__name__}\n")
ret = func(*args, **kwargs)
return ret
return inner
@log
def shoplist_add_2():
print("成功添加一个商品!")
@log
def shoplist_del_2():
print("成功删除一个商品!")
shoplist_add_2()
shoplist_del_2()
print("装饰器案例-2.日志(结束)".center(100, "*"))
def test_temp_3(self):
'''
1.编写一个函数,用户输入一个url,自动下载页面的结果
2.编写一个装饰器,实现缓存网页内容的功能
具体:实现下载的也买你存放于文件中,如果文件中有值(文件大小不为0),就优先从文件中读取网页内容,否则,就下载网页内容
:return:
'''
print("\n" + "装饰器案例-3.缓存(开始)".center(100, "*"))
from urllib.request import urlopen
import os
def cache(func):
def inner(*args, **kwargs):
if os.path.getsize(r"D:\Mytest\Mysvn\python\myfiles\my_course\django\1_script\test_txt"):
with open("test_txt", "rb") as f:
return f.read()
ret = func(*args, **kwargs)
with open("test_txt", "wb") as f:
f.write(b"****" + ret) # 加星号表示是下载的网页内容,不加星号表示文件里面的的缓存内容
return ret
return inner
@cache
def get(url):
code = urlopen(url).read()[0:100]
return code
ret_byte = get("http://www.baidu.com")
print(ret_byte)
print("装饰器案例-3.缓存(结束)".center(100, "*"))
if __name__ == '__main__':
# Test().test_temp_1()
# Test().test_temp_2()
# Test().test_temp_3()
# Test().test_temp_3()
pass