Python 装饰器进阶

一、装饰器的执行流程

上一篇文章介绍了装饰器的概念和基本使用,这篇我们来深入探索一下 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
  1. 首先将index函数内存地址作为参数传递到decorator中,func变量作为接收参数,并返回了inner这个函数的内存地址。
  2. 此时 func = index(index的内存地址赋值给func变量)index = inner (inner的内存地址赋值给index变量)
  3. 通过打印发现func变量的地址和最初的index 函数地址是一样的
  4. 第15行代码,将inner函数的内存地址赋值给了index函数。此时的index函数就是inner函数。故在执行index函数就是执行的decorator函数内的inner函数。
  5. inner函数中,打印了func变量的内存地址,是最初的index函数地址并执行了func() ,此时就会将输出:index 内存打印出来
  6. 最后打印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
  1. 开始看结果是不是有点懵,我们仔细发现第一行输出的decorator1,可以发现最先执行的是inner1这个函数。第二行输出的是inner2函数地址,表示我们的decorator1 中的func指向的是decorator2 中的inner2函数。
  2. 第三行输出decorator2,表示执行了decorator2 中的inner2函数,第四行输出了foo函数,表示decorator2 中的func指向的foo函数。
  3. 最后输出了foo函数本身的内容
  4. 执行流程图如下:
decorator1() --> inner1() --> decorator2() --> inner2() --> foo()
  1. 得出结论: 在多层装饰器中,执行流程是从上到下执行的。
  • 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
  1. 第一步是获取装饰器中的指定的请求方法列表,变量request_method_list 来接收装饰器中的参数。
  2. 第二步get_book_list函数就是inner函数,通过request获取到当前的请求方式,如果当前请求方式不在定义的请求列表中,则返回请求方法不允许。在请求列表中则执行get_book_list函数

是不是发现装饰器还是很简单的。好了,以上就是装饰器进阶的全部内容,本人水平有限,若文章存在错误欢迎在评论区指出,谢谢~

参考地址:https://www.runoob.com/w3cnote/python-func-decorators.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值