Python函数式编程——装饰器
1.什么是装饰器
先看一段代码:
# -*- coding: utf-8 -*-
# @File : 什么是装饰器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 19:38
def fun():
print('洗脸')
fun() # 调用fun函数
# 洗脸
# 想给fun函数增加 起床 和 吃早点 这两个功能
def test1(func):
def test2():
print('起床')
func()
print('吃早点')
return test2
test1(fun)()
# 起床
# 洗脸
# 吃早点
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。也可以将函数赋值变量,做参传入另一个函数。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值 也是一个函数对象。它经常用于有以下场景,比如:
插入日志
、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。
2.装饰器的作用
装饰器的作用:为已经存在的对象添加额外的功能
看代码代码:
# -*- coding: utf-8 -*-
# @File : 装饰器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 19:46
def test1(func):
def test2():
print('起床')
func()
print('吃早点')
return test2
@test1 # 装饰器
def fun():
print('洗脸')
fun()
# 起床
# 洗脸
# 吃早点
我们没有直接将fun
函数作为参数传入test1
中,只是将test1
函数以@方式装饰在fun
函数上。也就是说,被装饰的函数,函数名作为参数,传入到装饰器函数上,不影响
fun
函数的功能,再此基础上可以根据业务或者功能增加条件或者信息。
(注意:@
在装饰器这里是作为Python
语法里面的语法糖写法,用来做修饰。)
但是我们这里就存在一个问题,这里引入魔术方法__name__
,这是属于python
中的内置类属性,它天生就存在于一个python
程序中,代表对应程序名称,一般一段程序作为主线运行程序时其内置名称就是__main__
,当自己作为模块被调用时就是自己的名字。
print(fun.__name__)
# test2
这并不是我们想要的!输出应该是fun
,这里的函数被test2
替代了。它重写了我们函数的名字和注释文档,那怎么阻止变化呢,Python提供functools
模块里面的wraps
函数解决了问题。
代码实现:
# @Time : 2022/8/21 20:04
from functools import wraps
def test1(func):
@wraps(func) # 使用fun来包装test2
def test2():
print('起床')
func()
print('吃早点')
return test2
@test1 # 装饰器
def fun():
print('洗脸')
fun()
print(fun.__name__) # fun
我们在装饰器函数内,作用fun
的test2
函数上也增加了一个装饰器wraps
还是带参数的,这个装饰器的功能就是不改变使用装饰器原有函数的结构。
3.装饰器记录日志功能
为work()
函数增加记录日志功能
# -*- coding: utf-8 -*-
# @File : 装饰器实现记录日志功能.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 20:09
import time
from functools import wraps
def log(fun):
@wraps(fun)
def write_log():
print("[info]--时间是:%s" % time.strftime("%Y-%m-%d %H:%M:%S"))
fun()
return write_log
@log
def work():
print("我在学习Python")
work()
4.带参装饰器
我们也看到装饰器
wraps
也是带参数的,那我们是不是也可以定义带参数的装饰器呢,我们可以使用一个函数来包裹装饰器,调入这个参数。
# -*- coding: utf-8 -*-
# @File : 带参数的装饰器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 21:01
import time
from functools import wraps
def logs(func):
@wraps(func)
def write_log(*args, **kwargs):
print('[info]--时间:%s' % time.strftime('%Y-%m-%d %H:%M:%S'))
func(*args, **kwargs)
return write_log
@logs
def work():
print('我在工作')
@logs
def work2(name1, name2):
print('%s和%s在工作' % (name1, name2))
work2('张三', '李四')
# [info]--时间:2022-06-24 18:04:04
# 张三和李四在工作
5.函数做装饰器
把日志写入文件
# -*- coding: utf-8 -*-
# @File : 把日志写入文件.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 21:14
import time
from functools import wraps
def logger(log_file):
def logs(fun):
@wraps(fun)
def write_log(*args, **kwargs):
log = "[info]--时间是:%s" % time.strftime('%Y-%m-%d %H:%M:%S')
print(log)
with open(log_file, 'a+') as f:
f.write(log+'\n')
fun(*args, **kwargs)
return write_log
return logs
@logger('work.log') # 使用装饰器来给 work函数增加记录日志的功能
def work(name1, name2): # 1.当前 work可能有多个参数 2.自定义日志文件的名字和位置,记录日志级别
print(f"{name1}和{name2}正在工作")
work('张三', '李四')
终端输出:work.log
文件记录日志
这里我们将带参数的带入进去根据代码流程执行生成了文件并将日志信息打印进去现在我们有了能用于正式环境的
logger
装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。
6.类做装饰器
# -*- coding: utf-8 -*-
# @File : 把日志写入文件.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 21:14
import time
from functools import wraps
class Log(object):
def __init__(self, log_file, level="INFO"):
self.log_file = log_file
self.level = level
def __call__(self, fun): # 定义装饰器,需要有一个接受函数
@wraps(fun)
def write_log(*args, **kwargs):
log = "[%s]--时间是:%s" % (self.level, time.strftime('%Y-%m-%d %H:%M:%S'))
print(log)
with open(self.log_file, 'a+') as f:
f.write(log+'\n')
fun(*args, **kwargs)
return write_log
@Log('work3.png')
def learn(name):
print(f"{name}在学习Python")
@Log('work2.log') # 使用装饰器来给 work函数增加记录日志的功能
def work(name1, name2): # 1.当前 work可能有多个参数 2.自定义日志文件的名字和位置,记录日志级别
print(f"{name1}和{name2}正在工作")
learn("Flyme awei")
work('张三', '李四')
类实现装饰器有一个优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法。