python笔记11:装饰器
2022-04-19 本文已影响0人
_百草_
1. 什么是装饰器
函数也是一个对象,并且可以赋值给变量==>通过变量调用该函数
def main():
demo_func()
if __name__ == "__main__":
a = main # 函数对象赋值给变量;这里没有使用小括号,因为我们并不是在调用main函数
a() # 通过变量调用该函数
print(a.__name__) # 获取函数名 输出:main
print(main.__name__) # 获取函数名 输出:main
# 删除对象
del main
a() # 再次调用,正常
main() # 再次调用报错;NameError: name 'main' is not defined
期望:增强main()函数功能,如执行前后打印log,但不修改main()函数定义
==>在代码运行期间增加功能的方式,称之为“装饰器”
本质上,装饰器decorator
是一个返回函数的高阶函数
即:改变其他函数功能的函数
通过装饰器函数,来增强原函数的一些功能,且原函数不需要修改
1.1 在函数中定义函数
def main():
print("这是main函数!")
def hello():
print("这是Hello函数!")
def world():
print("这是World函数!")
return hello(), world() # 调用函数内函数
main() # 调用main(),函数内函数hello()和world()将同时调用
hello() # 函数main()外,无法直接访问函数内函数hello(),报错NameError: name 'hello' is not defined
1.2 从函数中返回函数
def main(name=""):
def welcome():
return "welcome"
def other():
return name
if name:
return other # 不需要在一个函数中执行另一个函数
else:
return welcome
a = main("hello") # 若不加小括号则是赋值,不是调用
print(a) # 输出<function main.<locals>.other at 0x000001271DDAE1F0>
print(a()) # a 指向函数,返回加小括号调用该函数
1.3 将函数作为参数传给另一函数
def main():
return "这是main函数!"
def before(func): # 一般func代指函数
print("执行前调用")
print(func()) # 函数调用
before(main) # 注意传参是函数,此处不用小括号
1.4 第一个装饰器
# 1---------------------
# # 没有返回函数,报错:TypeError: 'NoneType' object is not callable
def print_log(func):
print(f"开始执行{func.__name__}!")
func()
print(f"结束执行{func.__name__}!")
# 1---------------------
# 2--------------------
# 问题:函数func重复执行
def print_log(func):
print(f"开始执行{func.__name__}!")
func()
print(f"结束执行{func.__name__}!")
return func
# 2--------------------
# 3 使用函数内函数添加功能,函数返回该内部函数即可
def print_log(func): # 接收一个函数作为参数
def wrapper():
print(f"开始执行{func.__name__}!")
func()
print(f"结束执行{func.__name__}!")
return wrapper # 返回一个函数
# 方式1:使用装饰器的函数上方添加@func_name
@print_log
def main():
print("正在执行main...")
main()
# # 方式2:直接调用
# a = print_log(main)
# a()
装饰器调用:
@
称之为语法糖,相当于a = print_log(main)
@log # 使用@decorator_name ,放置在函数定义前;
# 注意:使用的装饰器(函数),后面不需要添加小括号
def main():
print("main")
在面向对象OOP
的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现。
- 支持OOP的decorator
- 从语法层次支持decorator。Python的
decorator
可以用函数实现,也可以用类实现。
- 缺点:定义起来虽然有点复杂
- 优点:使用起来非常灵活和方便;加强函数功能
2. 简单的装饰器
装饰器常用场景:日志、授权
通常情况下,会把*args
和**kwargs
,作为装饰器内部函数wrapper()
的参数=>表示接受任意数量和类型的参数。
import functools
# 装饰器
def decorator_name(func):
@functools.wraps(func)
def decorated(*args, **kwargs):
if not can_run:
return "function will not run"
return func(*args, **kwargs)
return decorated
@decorator_name
def funct(name):
return f"Function is running!-name={name}"
can_run = True
print(funct("wlh")) # 输出:Function is running!-name=wlh
can_run = False
print(funct("百草")) # 输出:function will not run
3. 带参数的装饰器
- 3层嵌套,定义带参数的装饰器
- 装饰器使用时,使用单一函数作为参数的
- 需要带带参数的装饰器时,需要再添加一个包裹函数,返回简单的装饰器即可
from functools import wraps
def log_var(filename): # 创建一个包裹函数
def print_log(func): # 简单装饰器
@wraps(func)
def wrapper(*args, **kwargs): # 为另一函数附加功能的函数
print("开始执行")
with open(filename, "a", encoding="utf-8") as f:
f.write("开始执行!")
func(*args, **kwargs)
print("结束执行")
with open(filename, "a", encoding="utf-8") as f:
f.write("结束执行!")
return wrapper
return print_log # 返回简单装饰器
4. 类装饰器
类也可以用来构建装饰器
类装饰器,主要依赖于函数__call__
,每当调用一个实例时,函数__call__
就会被执行一次。
from functools import wraps
class Log:
"""
比方说有时你只想打日志到一个文件。
而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。
这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
"""
def __init__(self, filename="out.log"):
self.filename = filename
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print("开始执行!")
print(self.filename)
with open(self.filename, 'a', encoding="utf-8") as f:
# 打印日志
f.write("开始执行!\n")
self.notify() # 发送通知
return func(*args, **kwargs)
return wrapper
def notify(self):
"""发送通知"""
print("发送通知")
pass
# 继承,创建子类
class EmailLog(Log):
"""一个logit的实现版本,可以在函数调用时发送email给管理员"""
def __init__(self, email, *args, **kwargs):
self.email = email
super(EmailLog, self).__init__(*args, **kwargs) # 继承类
def notify(self):
# 发送邮件到指定邮箱
print("发送邮件到指定邮箱")
# @Log() # 调用修饰器,注意类装饰器使用时,需要小括号
# def main():
# print("执行main")
#
#
# main()
@EmailLog("123")
def two():
print("执行two")
two()
5. 装饰器嵌套
6. @functools.wrap装饰器使用
print(main.__name__) # 输出结果:wrapper;即重写了函数的名字和注释文档docstring
print(main.__doc__) # 输出结果:这是wrapper的docstring
"""
使用functools.wraps来解决函数名和注释文档的重写
"""
from functools import wraps
def new_decorator(func):
"""这是new_decorator的docstring"""
@wraps(func) # 在定义的wrapper上添加休修饰器即可
def wrapper():
"""这是wrapper的docstring"""
print("执行前!")
func()
print("执行后!")
return wrapper
@new_decorator
def requiring_decorator():
"""这是requiring_decorator的docstring"""
print("这是requiring decorator函数!")
print(requiring_decorator.__name__)
print(requiring_decorator.__doc__)
注: