一、装饰器的执行流程
上一篇文章介绍了装饰器的概念和基本使用,这篇我们来深入探索一下 python 的装饰器。
1.1 简单装饰器例子
我们先看一个例子,控制台会输出什么?
def decorator(func):
def inner():
print("inner start")
func()
print("inner end")
return inner
@decorator
def index():
print("index")
index()
>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
inner start
index
inner end
相信有基础的同学已经知道为什么是这样打印的内容了。我们来仔细分析一下整个执行流程:
index
函数上面的@
符号就是下面这一行代码的语法糖
index = decorator(index)
1.2 装饰器本质
装饰器的本质就是上面那一行代码。index = decorator(index)
接下来我们看这段代码:
def decorator(func):
def inner():
"""操作"""
print("func 变量的地址: ", id(func))
func()
return inner
def index():
print("输出:index")
print("最初的 index 地址:", id(index))
inner = decorator(index)
print("返回的 inner 地址:", id(inner))
index = inner
print("inner赋值给index后的地址:", id(index))
index()
print("index 函数的 name 属性:"index.__name__)
>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
最初的 index 地址: 2336665790064
返回的 inner 地址: 2336665898576
inner赋值给index后的地址: 2336665898576
func 变量的地址: 2336665790064
输出:index
index 函数的 name 属性: inner
- 首先将
index
函数内存地址作为参数传递到decorator
中,func变量
作为接收参数,并返回了inner
这个函数的内存地址。 - 此时
func = index(index的内存地址赋值给func变量)
,index = inner (inner的内存地址赋值给index变量)
- 通过打印发现
func变量
的地址和最初的index 函数地址
是一样的 - 第15行代码,将
inner
函数的内存地址赋值给了index
函数。此时的index
函数就是inner
函数。故在执行index
函数就是执行的decorator
函数内的inner
函数。 inner
函数中,打印了func
变量的内存地址,是最初的index
函数地址并执行了func()
,此时就会将输出:index
内存打印出来- 最后打印index函数的
__name__
属性发现,更新后的index
函数就是inner
函数。
使用语法糖装饰函数,发现最后输出的__name__
属性也是inner
。
# 使用语法糖符号@ 给index函数添加装饰器decorator
def decorator(func):
def inner():
func()
return inner
@decorator
def index():
print("输出:index")
index()
print("index 函数的 name 属性:", index.__name__)
>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
输出:index
index 函数的 name 属性: inner
1.3 functools
1.2 小节中,给index函数添加装饰器后,此时index函数本质指向的装饰器内部的inner函数,如果我们想让index函数不要指向inner函数,而是指向原来的index函数。就需要使用functools包
from functools import wraps
def decorator(func):
@wraps(func)
def inner():
func()
return inner
@decorator
def index():
print("输出:index")
index()
print("index 函数的 name 属性:", index.__name__)
>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
输出:index
index 函数的 name 属性: index
上面就是装饰器的本质了,若写得不清楚欢迎在评论区留言。
1.4 实现用户认证的简单案例
def wrapper(func):
def inner(*args, **kwargs):
if not kwargs['token']:
print('您需要先登录')
if not kwargs['token'] == 6666: # 这里也可以写成数据库查询操作
print('认证已失效,请重新登录')
else:
f = func(*args, **kwargs)
return f # 可以实现多装饰器的传递
return inner
@wrapper
def order(token):
print('这是用户订单')
v = order(token=6666)
二、类装饰器
第一节都是讲述的函数装饰器,这一节我们来了解类装饰器
当前有一个需求:有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
幸运的是,类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式。
import settings
from functools import wraps
from datetime import datetime
class LogHandler:
# settings.LOG_FILE = "log.txt" ,可以把配置信息抽离到配置文件中
# settings.EMAIL_NOTIFY = False
def __init__(self, log_file=settings.LOG_FILE, send_email_status=settings.EMAIL_NOTIFY): # 接收传递参数
self.log_file = log_file
self.send_email_status = send_email_status
def __call__(self, func):
@wraps(func)
def decorator(*args, **kwargs):
current_time = datetime.now()
with open(self.log_file, 'a') as fp: # 写入数据
fp.write(f"[{current_time}] {func.__name__} is write log \n")
### 这里可以对用户登录进行用户名认证,通过才能发送邮件 ##
if self.send_email_status: # 需要发送邮件
self.send_email()
return func(*args, **kwargs)
return decorator
def send_email(self):
print("send email")
@LogHandler(send_email_status=True) # 若不传递参数,则使用默认参数
def login():
print("登录")
login()
查看log文件内容
在LogHandler类中,我们定义了__init__方法
和 __call__
方法,
__init__
会在类进行初始化时被调用 ,log_handler = LogHandler()
调用的init
方法__call__
可以使实例能够像函数一样被调用,同时不影响实例本身的生命周期(__call__()
不影响一个实例的构造和析构)。但是__call__()
可以用来改变实例的内部成员的值。log_handler(func)
调用的call
方法
所以上述代码可以拆解为:
log_handler = LogHandler(params1, params2) # 执行init
login = log_handler(login) # 执行call
login()
三、多层装饰器
当我们给一个函数套上多层装饰器时,它们的执行流程时怎样的呢?下面我们来看一段代码
def decorator1(func):
def inner1(*args,**kwargs):
print("decorator1")
print(func) # func => inner2 函数内存地址 (注释不明白看底下的解释)
func()
return inner1
def decorator2(func):
def inner2(*args,**kwargs):
print("decorator2")
print(func) # func => foo 函数内存地址 (注释不明白看底下的解释)
func()
return inner2
@decorator1
@decorator2
def foo():
print("foo")
foo()
>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
decorator1
<function decorator2.<locals>.inner2 at 0x000002200C7264C0>
decorator2
<function foo at 0x000002200CDAB820>
foo
- 开始看结果是不是有点懵,我们仔细发现第一行输出的
decorator1
,可以发现最先执行的是inner1
这个函数。第二行输出的是inner2
函数地址,表示我们的decorator1
中的func
指向的是decorator2
中的inner2
函数。 - 第三行输出
decorator2
,表示执行了decorator2
中的inner2
函数,第四行输出了foo
函数,表示decorator2
中的func
指向的foo
函数。 - 最后输出了
foo
函数本身的内容 - 执行流程图如下:
decorator1() --> inner1() --> decorator2() --> inner2() --> foo()
- 得出结论: 在多层装饰器中,执行流程是从上到下执行的。
- foo函数的具体初始过程是怎样的呢?
foo函数的初始过程是从下到上的,执行过程是从上到下的
# 1.先执行decorator2函数,并见foo函数地址传入
foo = decorator2(foo)
# 赋值后:foo = inner2 函数的内存地址, func 指向开始的foo内存地址
# 2.再执行decorator1函数,并将foo函数传入(foo函数本质是inner2 函数内存地址,即decorator1(inner2))
foo = decorator1(foo)
# 2.1 此时decorator1函数中 func 指向的是inner2 函数的内存地址
# 2.2 赋值后:foo = inner1 函数的内存地址
# 3.执行foo函数
foo()
# 3.1 等同于执行 decorator1函数中 inner1() 函数
以上就是多层装饰器的指向流程了。
四、带参数的装饰器
现在我们有一个需求:接口要实现一个权限认证,如果用户有这个权限,则允许访问这个api,否则不返回值。
类似结构如下,
@auth_permission(["VIEW","EDIT"])
def get_book_list(request):
"""
获取book列表
"""
- 实现
from functools import wraps
def auth_permission(permission_list):
def decorator(func):
@wraps(func)
def inner(*args, **kwargs):
user_id = kwargs.get("user_id",None)
if user_id:
# 获取该用户数据库中的所有权限,这里使用了django的 ORM,也可以使用sql语句
# permission_codes = models.UserPermission.objects.filter(user_id=user_id).first()
# 为了测试,手动定义用户id
if user_id == 1: # id=1的用户只有VIEW权限,
permission_codes = ["VIEW"]
else: # 其他用户为"DELETE","EDIT" 权限
permission_codes = ["DELETE","EDIT"]
# any() 函数表示只要有一个为True,就返回True
result = any(permission in permission_codes for permission in permission_list) # 表示设置的权限有一个在用户的所有权限中就返回True
if result:
return func()
else:
return "当前用户权限不够"
else:
return "请传递user_id"
return inner
return decorator
@auth_permission(["DELETE","EDIT"])
def get_book():
"""
获取book列表
"""
data = {
"id": 1,
"name": "骆驼祥子"
}
print(data)
-
测试
-
添加了参数后的初始化过程是怎样的呢?
可以发现在上面的代码中,我们嵌套了2层闭包。这2层闭包具体指代的是什么呢?
# 1. 先执行函数auth_permission,并见参数传入
decorator = auth_permission(["xx"])
# 2.此时@auth_permission(["xx"]) = @decorator
@decorator
def get_book():
"""
获取book列表
"""
data = {
"id": 1,
"name": "骆驼祥子"
}
print(data)
# 3. 将函数get_book 传入decorator ,
# 即 get_book = decorator(get_book) ,这一步发现是不是就是前面的函数装饰器,func 指代 get_book函数地址
get_book(user_id=1)
# *args 接收一个非键值对的参数,并以元组的形式返回, **kwargs 接收键值对的参数,并以字典返回
五、Django require_http_methods装饰器解读
我们来分析Django中的 require_http_methods
这个装饰器。
require_http_methods
这个装饰器可以指定前端的请求方式是POST
请求还是GET
请求
from django.views.decorators.http import require_http_methods
@require_http_methods(['POST'])
def get_book_list(request):
"""
视图函数操作
"""
return JsonResponse({'code': 0, 'results': "successs"})
我们点进去查看源码:
def require_http_methods(request_method_list):
"""
Decorator to make a view only accept particular request methods. Usage::
@require_http_methods(["GET", "POST"])
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
Note that request methods should be in uppercase.
"""
def decorator(func):
@wraps(func)
def inner(request, *args, **kwargs):
if request.method not in request_method_list:
response = HttpResponseNotAllowed(request_method_list)
log_response(
'Method Not Allowed (%s): %s', request.method, request.path,
response=response,
request=request,
)
return response
return func(request, *args, **kwargs)
return inner
return decorator
- 第一步是获取装饰器中的指定的请求方法列表,变量
request_method_list
来接收装饰器中的参数。 - 第二步
get_book_list
函数就是inner
函数,通过request
获取到当前的请求方式,如果当前请求方式不在定义的请求列表中,则返回请求方法不允许。在请求列表中则执行get_book_list
函数
是不是发现装饰器还是很简单的。好了,以上就是装饰器进阶的全部内容,本人水平有限,若文章存在错误欢迎在评论区指出,谢谢~
参考地址:https://www.runoob.com/w3cnote/python-func-decorators.html