一、装饰器的执行流程
上一篇文章介绍了装饰器的概念和基本使用,这篇我们来深入探索一下 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 innerdef 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 = Falsedef __init__(self, log_file=settings.LOG_FILE, send_email_status=settings.EMAIL_NOTIFY): # 接收传递参数self.log_file = log_fileself.send_email_status = send_email_statusdef __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 decoratordef 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 inner1def 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()# 为了测试,手动定义用户idif user_id == 1: # id=1的用户只有VIEW权限,permission_codes = ["VIEW"]else: # 其他用户为"DELETE","EDIT" 权限permission_codes = ["DELETE","EDIT"]# any() 函数表示只要有一个为True,就返回Trueresult = any(permission in permission_codes for permission in permission_list) # 表示设置的权限有一个在用户的所有权限中就返回Trueif result:return func()else:return "当前用户权限不够"else:return "请传递user_id"return innerreturn 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 responsereturn func(request, *args, **kwargs)return innerreturn decorator
- 第一步是获取装饰器中的指定的请求方法列表,变量
request_method_list
来接收装饰器中的参数。 - 第二步
get_book_list
函数就是inner
函数,通过request
获取到当前的请求方式,如果当前请求方式不在定义的请求列表中,则返回请求方法不允许。在请求列表中则执行get_book_list
函数
是不是发现装饰器还是很简单的。好了,以上就是装饰器进阶的全部内容,本人水平有限,若文章存在错误欢迎在评论区指出,谢谢~
参考地址:https://www.runoob.com/w3cnote/python-func-decorators.html