python:从入门到放弃 10 装饰器
装饰器简介
装饰器的本质:在不改变被装饰对象原有的调用方式
和内部代码
、的情况下给被装饰对象添加新的功能
软件的设计应该遵循开放封闭原则,即对扩展是开放
的,而对修改是封闭
的。
软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
装饰器
# 首先定义一个函数
def foo():
print("foo")
# 如果我们需要修改它的功能
def foo():
print("记录日志开始")
print("foo")
print("记录日志结束")
'''可以看见的是,我们修改了他的内部代码,这有可能导致程序崩溃'''
# 使用装饰器修改函数的功能
def outer(func): # 装饰器
def inner():
print("记录日志开始")
func() # 业务函数
print("记录日志结束")
return inner
def foo():
print("foo") # foo函数的内部代码没有被修改
foo = outer(foo)
foo() # foo函数的调用方法没有被修改
'''outer 函数的返回值是 inner 函数,在 inner 函数中,除了执行日志操作,还有业务代码,该函数重新赋值给 foo 变量后,调用 foo() 就相当于调用 inner()'''
'''可以看见的是,outer函数就是一个装饰器,即装饰器是一个带有函数作为参数并返回一个新函数的闭包'''
装饰器模板
'''编写装饰器其实有一套固定的代码 不需要做任何理解'''
def outer(func_name): # func_name用于接收被装饰的对象(函数)
def inner(*args, **kwargs):
print('执行被装饰函数之前 可以做的额外操作')
res = func_name(*args, **kwargs) # 执行真正的被装饰函数
print('执行被装饰函数之后 可以做的额外操作')
return res # 返回真正函数的返回值
return inner
语法糖
# 仅仅是让代码编写的更加好看、简洁!!!
@outer # foo = outer(foo) 省去了手动给foo重新赋值的步骤。
def foo():
print("foo")
foo()
'''语法糖内部原理
1.使用的时候最好紧跟在被装饰对象的上方
2.语法糖会自动将下面紧挨着的函数名传给@后面的函数调用'''
装饰器修复技术
Python装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),而python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。它能保留原有函数的名称和注释。
from functools import wraps
def outer(func_name):
@wraps(func_name) # 使用wraps装饰器
def inner(*args, **kwargs):
'''这是inner函数的注释'''
print('执行被装饰对象之前可以做的操作')
res = func_name(*args, **kwargs)
return res
return inner
@outer
def index():
print('from index')
@outer
def home():
'''这是home函数的注释'''
print('from home')
help(home)
# home() 输出的是home的函数名
# 这是home函数的注释 输出的是home的注释
如果我们将wraps装饰器注释
# from functools import wraps
def outer(func_name):
# @wraps(func_name) # 不使用wraps装饰器
def inner(*args, **kwargs):
'''这是inner函数的注释'''
print('执行被装饰对象之前可以做的操作')
res = func_name(*args, **kwargs)
return res
return inner
@outer
def index():
print('from index')
@outer
def home():
'''这是home函数的注释'''
print('from home')
help(home)
# inner(*args, **kwargs) 输出的是inner的函数名
# 这是inner函数的注释 输出的是inner的注释
多层装饰器
"""语法糖会将紧挨着的被装饰对象的名字当做参数自动传入装饰器函数中"""
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 # 装饰outter2的返回值
@outter2 # 装饰outter3的返回值
@outter3 # 装饰index
def index():
print('from index')
'''不使用语法糖'''
def index():
print('from index')
index = outter3(index)
index = outter2(index)
index = outter1(index)
index()
# 先就近装饰(即定义阶段) 后就远执行 分2个过程
加载了outter3
加载了outter2
加载了outter1
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
有参装饰器
有参装饰器其实就是一个套了2次的多层装饰器
装饰器最多只需三层,从外到内分别是'传参层','语法糖层','核心逻辑层',内两层的参数是固定不能动的,所以参数统一在最外层投给它。
而传参调用最外层函数后,得到的是'拿到所需参数的语法糖层',紧接着就跟以前两层的装饰器一样了。他和无参装饰器的区别在于我们可以规定他拿到的参数。
def outer(source_data):
def login_auth(func_name): # 不能动 只能p接收一个被装饰对象名字
def inner(*args, **kwargs): # 不能动 是专门用来给被装饰的对象传参的
if source_data == '1':
print('使用字典的方式处理数据')
elif source_data == '2':
print('使用列表的方式处理数据')
elif source_data == '3':
print('使用文件操作处理数据')
else:
print('其他操作情况')
res = func_name(*args, **kwargs)
return res
return inner
return login_auth
@outer('2') # 传入字符串2
def index():
pass
# 在普通的装饰器外层加一个传参层 在不改变语法糖层和核心层的情况下,根据传参不同,改变输出