初步认识
给已有函数增加额外功能的函数,但不改变原有函数。
装饰器本质上也是一种闭包函数,装饰器函数的参数仅有一个且是函数类型
举个例子,你有一个函数logic,作用是查询信息等逻辑。现在需求是:要在查询信息之前做身份认证,怎么办?
通常你可以在logic函数中的查询信息之前增加身份认证的逻辑,从而达到要求,如下:
# 代码块一
# 原函数
def logic():
print('正在查询信息...')
# 增加认证逻辑后
def logic():
print('正在认证...') # 函数体就被修改了
print('正在查询信息...')
可以发现,这样改变了这个函数本身。而使用装饰器不改变原有函数。
先初步看看装饰器的写法,如下:
# 代码块二:原始装饰器的写法
# 原函数
def logic():
print('正在查询信息...')
# 装饰器函数
def decorator(func):
def inner():
print('正在认证...')
func() # 原 logic 函数在此被调用
return inner
# 调用
new_logic = decorator(logic)
new_logic()
可以初步感觉得到,装饰器函数就是对原有函数进行”外部包装”
在python中,有专门的语法糖描述上述写法,如下:
# 代码块三:语法糖的写法
# 装饰器函数
def decorator(func):
def inner():
authenticate()
func()
return inner
# 语法糖
@decorator
def logic():
print('正在查询信息...')
# 调用
logic() # 由于语法糖中 decorator 的装饰作用,此 logic 函数具备了认证、查询的功能
装饰器特点
基于上述的简单认识,装饰器有如下有特点:
-
不修改已有函数的源代码
-
不修改已有函数的调用方式
-
给已有函数增加额外的功能
装饰器语法糖 @ 符号的分析
回顾上述代码块二,定义了装饰器函数decorator后,将logic函数作为装饰器函数decorator的参数传入,赋给new_logic变量,然后执行new_logic()。
而 @ 符号的左右正是这一过程的描述,只不过 @ 符号赋给的变量不是重新命个名(如上述赋给变量new_logic),而是赋给原函数这个变量,即 logic = decorator(logic),然后执行logic()
def logic():
print('正在查询信息...')
def decorator(func):
def inner():
print('正在认证...')
func()
return inner
logic = decorator(logic) # 赋给原logic变量
logic() # 此时调用的logic不再是原logic,而是被装饰后的logic
-------------------------------上下对比---------------------------------
def decorator(func):
def inner():
print('正在认证...')
func()
return inner
@decorator
def logic():
print('正在查询信息...')
# 调用
logic()
@符号的本质逻辑就是,将被装饰的函数func作为装饰器decorator的参数传入,装饰器返回的inner(实质是一个闭包)赋给原函数func变量,当调用func时,实质是调用已经被装饰了的func
装饰器的副作用
细心的你会发现,装饰后赋给原函数变量,意味着原函数将无法被调用。因为调用的函数是已经被装饰了的函数。
PS:一般情况下,不再需要原函数,也就不需要调用原函数了,那这个副作用可以忽略,因此下一句话初学者可暂时不用理解
准确的说,原函数将无法被直接调用。装饰时会使原函数的部分属性被改变,如__name__,所以调用到的是装饰后的函数,若想调用原函数,可使用from functools import wraps 的wraps方法,这里不做详述
装饰过程 与 执行过程
装饰过程指的是,装饰器函数对原函数的作用过程。
执行过程指的是,对已被装饰了的函数的调用过程
python程序在启动时,将py源码编译成字节码,然后解释执行。而装饰过程就发生在程序运行之前!也就是说,运行代码之前,装饰器函数对原函数的作用过程已执行完毕,即@符号代表的装饰过程已经被解释了。然后代码运行时,才真正调用被装饰的函数。
从多个装饰器作用的角度看 装饰过程 与 执行过程
以下模拟一个苹果,先被装入一个塑料袋,然后一起被装入一个箱子
def add_plastic(func):
print(' --- add_plastic 装饰器执行了 --- ')
def inner1():
print(' --- 调用 add_plastic 装饰器的inner --- ')
result = '塑料袋' + func() + '塑料袋'
return result
return inner1
def add_box(func):
print(' --- add_box 装饰器执行了 --- ')
def inner2():
print(' --- 调用 add_box 装饰器的inner --- ')
result = '箱子' + func() + '箱子'
return result
return inner2
@add_box
@add_plastic
def content():
print(' --- 调用 content --- ')
return '####我是一个苹果####'
result = content()
print(result)
# 输出结果:
--- add_plastic 装饰器执行了 ---
--- add_box 装饰器执行了 ---
--- 调用 add_box 装饰器的inner ---
--- 调用 add_plastic 装饰器的inner ---
--- 调用 content ---
箱子塑料袋####我是一个苹果####塑料袋箱子
分析得出,
多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程。
实际函数调用时,装饰器内部函数inner执行的过程是:最外层装饰器的内部函数inner先执行
函数装饰器的类型 及 通用装饰器
基本原则:装饰器内部函数inner的参数类型以及是否有返回值与被装饰的函数保持一致!!!
根据不同的函数,装饰器的写法如下
# -----------------装饰带有参数的函数-------------------
def decorator(func):
def inner(a, b):
print('计算进行中...')
func(a, b)
return inner
@decorator
def add(num1, num2):
result = num1 + num2
print('结果为:', result)
add(1, 3)
# --------------装饰带有返回值的函数--------------------
def decorator(func):
def inner(a, b):
print('计算进行中...')
result = func(a, b)
return result
return inner
@decorator
def add(num1, num2):
result = num1 + num2
return result
result = add(2, 5)
print(result)
于是,有了通用装饰器写法,如下:
# 通用装饰器,即不管原函数啥样,装饰器函数都可以这么写
def decorator(func):
print('start decorate, you can do some decorate-related operation here ------')
def inner(*args, **kwargs):
print('you can do some other operation here before origin func ------')
return func(*args, **kwargs) # 函数没有返回值时,接收的为None
return inner
@decorator
def add(*args, **kwargs):
result = 0
# 若是位置参数则args接收
for value in args:
result += value
# 若是关键字参数则kwargs接收
for value in kwargs.values():
result += value
return result
result = add(1, 5)
print('result', result)
带参数的装饰器
带有参数的装饰器:本质是将装饰器封装在一个带参有返回值的函数里,这个返回值返回该装饰器。即装饰器外面再包装一个带参函数。
注意:不是装饰器函数本身的参数中增添其他参数,因为装饰器函数的参数有且仅有一个,且是函数类型
写法如下:
def return_decorator(flag):
# 对装饰器外包一个带参且有返回值函数,即可实现对装饰器的传参(这里的参数是decorator本身需要的,而不是inner需要的)
# def decorator(func, flag): # 不合法,flag无法接收
def decorator(func): # 装饰器函数,有且仅有一个函数类型的参数
def inner(a, b):
if flag == '+':
print('加法计算进行中...')
elif flag == '-':
print('减法计算进行中...')
func(a, b)
return inner
# 调用外包函数时,返回该装饰器,此时拿到了参数flag
return decorator
@return_decorator('+') # 相当于decorator = return_decorator('+') @decorator ==> add = decorator(add)
def add(a, b):
result = a + b
print(result)
@return_decorator('-')
def sub(a, b):
result = a - b
print(result)
add(1, 5)
sub(4, 2)
类装饰器 与 类实例装饰器
-
类装饰器
通过类来装饰函数func,相当于无参装饰器
class ClassDecorator(object):
def __init__(self, func): # 相当于装饰器函数
print('类的初始化过程就是装饰的过程, you can do some decorate-related operation here ------')
self.func = func
def __call__(self, *args, **kwargs): # 被装饰函数 func 有参数时被 args/kwargs接收
print('被装饰后,新函数(实例)被调用的执行 ------')
print('函数实参-----', args, kwargs)
print('you can add some other operation before origin func ------')
res = self.func(*args, **kwargs)
print('you can add some other operation after origin func ------')
return res
@ClassDecorator # 相当于 show = ClassDecorator(show), 此时的 show 就是实例对象
def show(f1, f2):
add_sum = f1 + f2
print('----func show is executing------', add_sum)
return add_sum
# 装饰后的 show 即是一个ClassDecorator对象
print(show) # 输出 show,为 ClassDecorator 对象,此时并未执行 __call__ 方法
add_sum = show(10, f2=22) # show (实例)的调用即是 __call__ 的执行,若 __call__ 方法有返回值,则实例的调用有返回值
print(add_sum)
过程分析:show = ClassDecorator(show) 装饰过程就是类的初始化
被装饰函数将函数名func作为参数传给类,对类初始化返回一个实例对象,
并将这个实例对象赋给func(或者说将这个实例对象重命名为func)
以后对func的调用,就是对该类实例对象的调用,调用时会执行__call__方法
PS:函数本身也是类。函数之所以能够被调用,是因为函数类内部实现了__call__方法
-
类实例装饰器
通过实例来装饰函数func,可以在类初始化时进行传参,即带参装饰器。
如web框架flask的路由映射: @route(‘/index’, methods=[])
class InstanceDecorator:
def __init__(self, d1, d2): # 类初始化(即装饰器初始化)时传参,即带参装饰器
self.d1 = d1
self.d2 = d2
print('initialize decorator, you can do some operation related to decorator itself here')
self.extra_init()
def extra_init(self):
"""
这里可以根据参数d1, d2对装饰器进行初始化配置
"""
pass
def __call__(self, func): # 相当于装饰器函数,被装饰函数作为参数func传入
print('start decorate, you can do some decorate-related operation here ------')
def wrapper(*args, **kwargs): # 被装饰函数func有参数时被args/kwargs接收
print('函数实参-----', args, kwargs)
print('you can add some other operation before origin func ------')
res = func(*args, **kwargs)
print('you can add some other operation after origin func ------')
return res
return wrapper # 返回一个闭包wrapper(保存了原func函数),覆盖到原func,以后func的调用即是wrapper的调用
@InstanceDecorator(d1='dd11', d2='dd22')
def hello_func_without_params():
print('----hello_func_without_params is executing------')
print('hello_func1 ===========', hello_func_without_params) # 为一个闭包
hello_func_without_params()
@InstanceDecorator(d1='dd33', d2='dd44')
def hello_func_with_params(f1, f2):
print('----hello_func_with_params is executing------')
return f1 + f2
f12 = hello_func_with_params('aaa', f2='bbb')
过程分析:
装饰过程就是执行__call__的过程
装饰的过程中,可以进行适当操作,
装饰过程的最后是否return:
a. 没有return时,原被装饰函数func=None
b. 有return时,原被装饰函数func被覆盖为return的结果,通常为一个闭包