[Python]装饰器
前言
系列文章目录
[Python]目录
视频及资料和课件
链接:https://pan.baidu.com/s/1LCv_qyWslwB-MYw56fjbDg?pwd=1234
提取码:1234
文章目录
- 前言
- 1. 装饰器的定义
- 1.1 装饰器的功能特点
- 2. 装饰器的示例代码
- 3. 装饰器的语法糖写法
- 4. 装饰器的执行时机
- 5. 装饰器的使用
- 5.1 装饰器的使用场景
- 5.2 装饰器实现已有函数执行时间的统计
- 6. 通用装饰器的使用
- 6.1 装饰带有参数的函数
- 6.2 装饰带有参数和返回值的函数
- 6.3 装饰带有不定长参数和返回值的函数
- 6.4 通用装饰器
- 7. 多个装饰器的使用
- 8. 带有参数的装饰器
- 9. 类装饰器的使用
1. 装饰器的定义
装饰器就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
装饰器是一个闭包函数,也就是说装饰器是一个函数嵌套。
1.1 装饰器的功能特点
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
2. 装饰器的示例代码
需要实现一个功能:
在发表评论之前,添加一个"已进行登录验证"
# 需要实现一个功能:
# 在发表评论之前,添加一个"已进行登录验证"
# 定义装饰器
# 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器
def decorator(func):
def inner():
# 在内部函数里面对已有的函数进行装饰
print('已进行登录验证...')
func()
# 返回内部函数
return inner
# 先定义一个函数
def comment():
print('发表评论...')
# 调用装饰器对已有函数进行装饰, comment = inner
comment = decorator(comment)
# 调用定义的函数
comment()
代码说明:
闭包函数有且只有一个参数,并且该参数是函数类型,这样定义的函数才是装饰器。
写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展。
装饰器相当于原来的函数由闭包的外部函数进行保存,因为闭包内使用了外部函数的变量,且闭包函数没有进行释放,所以外部函数的变量无法进行释放,然后将原来声明的函数进行指向的改变,原来声明的函数指向返回的闭包。
使用装饰器对函数进行功能的拓展,其实是改变函数的指向,由闭包去调用原函数。
3. 装饰器的语法糖写法
如果有多个函数都需要添加登录验证的功能,每次都需要编写comment = decorator(comment)
这样代码对已有函数进行装饰,这种做法还是比较麻烦。
Python给提供了一个装饰函数更加简单的写法,那就是语法糖。
语法糖的书写格式是:
@装饰器名字
通过语法糖的方式也可以完成对已有函数的装饰.
# 需要实现一个功能:
# 在发表评论之前,添加一个"已进行登录验证"
# 定义装饰器
# 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器
def decorator(func):
def inner():
# 在内部函数里面对已有的函数进行装饰
print('已进行登录验证...')
func()
# 返回内部函数
return inner
# 先定义一个函数
# 使用语法糖的方法对 comment 函数进行装饰
# 此代码相当于 comment = decorator(comment)
# 对函数进行装饰后,comment = inner
@decorator
def comment():
print('发表评论...')
# 调用定义的函数
comment()
4. 装饰器的执行时机
装饰器的执行时机:
当当前模块加载完成后,装饰器会立即执行,对已有的函数进行装饰。
# 需要实现一个功能:
# 在发表评论之前,添加一个"已进行登录验证"
# 定义装饰器
# 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器
def decorator(func):
print('装饰器执行了...')
def inner():
# 在内部函数里面对已有的函数进行装饰
print('已进行登录验证...')
func()
# 返回内部函数
return inner
# 先定义一个函数
# 使用语法糖的方法对 comment 函数进行装饰
# 此代码相当于 comment = decorator(comment)
# 对函数进行装饰后,comment = inner
@decorator
def comment():
print('发表评论...')
# 调用定义的函数
# comment()
导入装饰器的模块
import pycode
上述代码说明:当当前模块加载完成后,装饰器会立即执行,对已有的函数进行装饰。
5. 装饰器的使用
5.1 装饰器的使用场景
- 对函数的功能进行拓展
- 函数执行时间的统计
- 输出日志信息
5.2 装饰器实现已有函数执行时间的统计
import time
# 定义装饰器
def decorator(func):
def inner():
# 获取函数执行开始的时间
begin = time.time()
# 调用执行函数
func()
# 获取函数执行结束的时间
end = time.time()
# 打印函数的执行时间
print('函数执行完成,用时:', end-begin)
# 返回内部函数
return inner
# 定义函数,并对其进行装饰
# work = decorator(work)
@decorator
def work():
sum = 0
for i in range(10000):
sum += i
print(sum)
# 调用执行函数
work()
6. 通用装饰器的使用
通用装饰器,即这个装饰器可以装饰任意的函数。
6.1 装饰带有参数的函数
使用装饰器装饰带有参数的函数时,装饰器的内部函数的类型需要和要进行装饰的已有函数的类型保持一致,即内部函数需要的参数个数要和需要进行装饰的函数一致。
# 定义装饰器
def decorator(func):
# 由于内部函数内执行的函数需要两个参数,所以这里需要接收两个参数
# 内部函数的类型要和需要进行装饰的已有的函数的类型一致
def inner(num1, num2):
# 在内部函数对已有函数进行装饰
print('正在执行函数...')
func(num1, num2)
return inner
# 进行装饰
@decorator
def add(num1, num2):
print('相加结果为:', num1+num2)
add(1, 2)
6.2 装饰带有参数和返回值的函数
使用装饰器装饰函数时,装饰器的内部函数的类型需要和要进行装饰的已有函数的类型保持一致。
装饰带有返回值的函数,装饰器的内部函数需要接收调用函数的返回值,与此同时并向外返回接收到的返回值。
# 定义装饰器
def decorator(func):
# 由于内部函数内执行的函数需要两个参数,所以这里需要接收两个参数
# 内部函数的类型要和需要进行装饰的已有的函数的类型一致
def inner(num1, num2):
# 在内部函数对已有函数进行装饰
print('正在执行函数...')
res = func(num1, num2)
return res
return inner
# 进行装饰
@decorator
def add(num1, num2):
# print('相加结果为:', num1+num2)
return num1 + num2
res = add(1, 2)
print('相加的结果为:', res)
6.3 装饰带有不定长参数和返回值的函数
*args
:接收参数后,为元组类型**kwargs
:接收参数后,为字典类型
该装饰器就是通用装饰器。
# 定义装饰器
def decorator(func):
# 由于内部函数调用的函数接收的不定长
# 所以此处接收的参数也为不定长
def inner(*args, **kwargs):
# 在内部函数对已有函数进行装饰
print('正在执行函数...')
# 注意,在次数传参时,不能直接将 args, kwargs 直接传入
# 直接传入 args, kwargs 会被作为两个单独的参数,一个元组类型,一个字典类型
# 所以这里需要先进行拆包
# 这里对元组和字典进行拆包,仅限于结合不定长参数的函数使用
res = func(*args, **kwargs)
return res
return inner
# 该函数接收不定长参数,不定长关键字参数
# 进行装饰
@decorator
def add(*args, **kwargs):
sum = 0
# 循环遍历 args,看是否有不定长参数传入函数
for val in args:
sum += val
# 循环遍历 kwargs,看是否有不定长关键字参数传入函数
# 由于次数不需要 键 ,所以只遍历 值
for val in kwargs.values():
sum += val
return sum
res = add(1, 2)
print('相加的结果为:', res)
装饰什么样的函数,内部函数就什么样。
6.4 通用装饰器
# 定义装饰器
def decorator(func):
# 由于内部函数调用的函数接收的不定长
# 所以此处接收的参数也为不定长
def inner(*args, **kwargs):
# 在内部函数对已有函数进行装饰
print('正在执行函数...')
# 注意,在次数传参时,不能直接将 args, kwargs 直接传入
# 直接传入 args, kwargs 会被作为两个单独的参数,一个元组类型,一个字典类型
# 所以这里需要先进行拆包
# 这里对元组和字典进行拆包,仅限于结合不定长参数的函数使用
res = func(*args, **kwargs)
# 进行装饰的函数没有返回值,这里接受和返回都为 None
return res
return inner
@decorator
def f1():
print('测试通用装饰器')
@decorator
def f2(num):
return num
f1()
res = f2(2)
print(res)
7. 多个装饰器的使用
使用多个装饰器装饰一个函数。
需要实现的功能:
在原函数输出一句话的基础上,在该句话前后加上<p>
和</p>
,在此基础上,再在前后加上<div>
和</div>
。
# 装饰器 1
def make_div(func):
print('make_div执行了...')
def inner():
# 对内部函数进行装饰
res = '<div>' + func() + '</div>'
return res
return inner
# 装饰器 2
def make_p(func):
print('make_p执行了...')
def inner():
# 对内部函数进行装饰
res = '<p>' + func() + '</p>'
return res
return inner
# 装饰执行过程:make_div( make_p( content ) )
@make_div
@make_p
def content():
return '人生苦短,我用Python'
res = content()
print(res)
从执行结果,可以看出,装饰的执行过程是从下向上进行装饰的
8. 带有参数的装饰器
带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,对不同的函数,传入不同的参数,可以进行不同的装饰。
语法格式:
@装饰器(参数,...)
注意:装饰器只能接收一个参数,且这个参数是函数类型。
# 创建一个函数,用于创建并返回装饰器
# 该函数接收的参数为 对函数不同的装饰参数
def make_decorator(dec):
# 定义装饰器
def decorator(func):
def inner(a, b):
# 对函数进行装饰
print(dec)
res = func(a, b)
return res
return inner
# 返回创建的装饰器
return decorator
# 定义函数,并对该函数进行装饰
# 装饰函数的过程:
# 1. 先执行函数 make_decorator() 得到装饰器 decorator
# 2. 使用得到的装饰器 decorator 装饰函数 decorator(add_num) 返回装饰后的函数
@make_decorator('正在进行加法计算...')
def add_num(a, b):
return a + b
# 调用函数
res = add_num(1, 2)
print('计算结果为: ', res)
9. 类装饰器的使用
使用类来装饰函数。
使用类装饰器装饰函数,需要实现__call__
方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。
# 定义类装饰器
class MyDecorator(object):
# 当使用类装饰器装饰函数,默认会调用 __init__ 方法
# 使用 __init__ 初始化对象
def __init__(self, func):
# 由于对函数进行装饰后,装饰前的函数不希望再次被调用
# 所以原函数设置为私有属性
self.__func = func
# 实现`__call__`方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。
def __call__(self, *args, **kwargs):
# 装饰函数
print('课讲完了')
self.__func()
# 使用类装饰器,此处相当于
# show = MyDecorator(show)
# 所以装饰以后为一个对象,对象能够调用需要实现 __call__ 方法
@MyDecorator
def show():
print('下课了...')
show()
拓展:函数之所以能够调用,是因为函数内部实现了 __call__
方法。
def test():
print('test()')
print(dir(test))