【Python】如何正确执行python装饰器?

news2025/2/27 20:45:48

文章目录

  • 前言
  • 一、错误的做法是什么?
  • 二、正确的方法
  • 总结


前言

说到Python装饰器的执行顺序,有很多半吊子张口就来:

靠近函数名的装饰器先执行,远离函数名的装饰器后执行。

这种说法是不准确的。但是这些半吊子多半还会不服,他们会甩出一段代码给你,来『证明』自己的观点:

一、错误的做法是什么?

def decorator_outer(func):
    print("我是外层装饰器")
    def wrapper():
        func()
    return wrapper

def decorator_inner(func):
    print("我是内层装饰器")
    def wrapper():
        func()
    return wrapper  

@decorator_outer
@decorator_inner
def func():
    print("我是函数本身")

func()

运行效果如下图所示:
在这里插入图片描述
decorator_inner这个装饰器靠近函数名,是内层装饰器,它里面的print先打印出来;decorator_outer远离函数名,是外层装饰器,它里面的print后打印出来。看起来确实是内层装饰器先执行,外层装饰器后执行。

为什么我说这种看法是不准确呢?我们来看看下面这段代码:

def decorator_outer(func):
    print("我是外层装饰器")
    print('a')
    print('b')
    def wrapper():
        print('外层装饰器,函数运行之前')
        func()
        print('外层装饰器,函数运行之后')
    print('外层装饰器闭包初始化完毕')
    print('c')
    print('d')
    return wrapper

def decorator_inner(func):
    print("我是内层装饰器")
    print(1)
    print(2)
    def wrapper():
        print('内层装饰器,函数运行之前')
        func()
        print('内层装饰器,函数运行之后')
    print('内层装饰器闭包初始化完毕')
    print(3)
    print(4)
    return wrapper  

@decorator_outer
@decorator_inner
def func():
    print("我是函数本身")

func()

上面这个代码的运行效果如下图所示:
在这里插入图片描述

从图中可以看到,装饰器里面的代码中,wrapper闭包外面的代码确实是内层装饰器先执行,外层装饰器后执行。但是在闭包wrapper内部的代码,却稍微复杂一些:

外层装饰器先执行,但只执行了一部分,执行到调用func()
内层装饰器开始执行
内层装饰器执行完
外层装饰器执行完
这个执行效果有点类似于:

def func():
    print('我是函数本身')

def deco_inner():
    print('内层装饰器,函数运行之前')
    func()
    print('内层装饰器,函数运行之后')

def deco_outer():
    print('外层装饰器,函数运行之前')
    deco_inner()
    print('外层装饰器,函数运行之后')

运行效果如下图所示,跟装饰器里面各个wrapper闭包的运行顺序是一致的。

在这里插入图片描述
所以,当我们说多个装饰器堆叠的时候,哪个装饰器的代码先运行时,不能一概而论说内层装饰器的代码先运行。这会给人一种错觉,认为是内层装饰器的代码从第一行到最后一行都是先运行的。准确的说法应该是,wrapper外面的代码,确实是内层装饰器先运行,外层装饰器后运行。但是wrapper里面的代码,是外层装饰器先开始运行,后运行完毕,内层装饰器后开始运行,先运行完毕。

这个知识看起来似乎有点像面试八股文,有什么用呢?我给大家举个例子。下面是使用FastAPI写的一个接口:

from fastapi import FastAPI
app = FastAPI()

def do_query_dataset(dataset_id):
    print("直接读取数据库,获取dataset信息")
    dataset_info = {"xxx": 1, "yyy": 2}
    return dataset_info


@app.get('/dataset')
def get_dataset(dataset_id: int):
    dataset_info = do_query_dataset(dataset_id)
    return {'success': True, "data": dataset_info}

用户访问这个接口,URL中传入参数dataset_id,就可以获得数据集的信息。如下图所示:

在这里插入图片描述

二、正确的方法

现在,要增加权限校验,首先要判断用户是否登录。在用户已经登录的情况下,看这个用户是否有这个数据集的权限。在有这个数据集的权限时,才能返回数据集信息。

你肯定想到了使用装饰器来做这两步,一开始你写的代码可能是这样的:

def check_login(func):
    def wrapper(*args, **kwargs):
        print('检测是否有特定的Cookies')
        is_login = False
        if not is_login:
            return {'success': False, "msg": "没有登录"}
        return func(*args, **kwargs)
    return wrapper


def check_data_set_permission(func):
    def wrapper(*args, **kwargs):
        print('检测是否有特定的数据集权限')
        print('首先从请求参数中获取dataset_id')
        print('然后从登录session中获取用户id,注意,如果没有登录,是没有session的')
        print('判断用户是否有这个dataset的权限')
        has_data_set_permission = True
        if not has_data_set_permission:
            return {'success': False, "msg": "没有数据集权限"}
        return func(*args, **kwargs)
    return wrapper

这个时候,我们要确保check_login里面检查用户是否登录的代码首先运行。然后才能是check_data_set_permission里面检查数据集权限的代码。

本文开头的半吊子,认为靠近函数名的装饰器先执行,远离函数名的装饰器后执行。按他们理论,就会写成:

@check_data_set_permission
@check_login
def do_query_dataset(dataset_id):

这样写显然是错误的。因为check_data_set_permission装饰器会有一个前提,就是用户已经登录了,代码才会走到这里。那么他就会直接去session取用户ID。没有登录的用户是没有用户ID的。在取ID的这一步就会出错。

根据本文上面的解释,由于这两个逻辑都是在wrapper内部的。 wrapper内部的代码,外层装饰器先开始运行。因此,这里我们装饰器的正确顺序,只能按照如下顺序排列:
@check_login
@check_data_set_permission
def do_query_dataset(dataset_id):

这个写法,从直觉上,就会跟本文开头的认知矛盾。但这才是正确的顺序。

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/426784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

海明码的计算和检错纠错

海明码 1.学习前提 学习海明码之前,我们要约定3个原则: 海明码只能检测出2位错,纠1位错(因此不要问如果3位错怎么办等幼稚问题)。海明码默认进行偶校验(除非特殊说明使用奇校验)。海明码是一串由0和1组成的序列(除01外没有其他的值&#x…

ASEMI代理AD8606ACBZ-REEL7原装ADI车规级AD8606ACBZ-REEL7

编辑:ll ASEMI代理AD8606ACBZ-REEL7原装ADI车规级AD8606ACBZ-REEL7 型号:AD8606ACBZ-REEL7 品牌:ADI/亚德诺 封装:WLCSP-8 批号:2023 引脚数量:8 安装类型:表面贴装型 AD8606ACBZ-REEL7…

Serverless 冷启动:如何让函数计算更快更强?

问题背景 Serverless 计算也称服务器无感知计算或函数计算,是近年来一种新兴的云计算编程模式。其致力于大幅简化云业务开发流程,使得应用开发者从繁杂的服务器运维工作中解放出来(例如自动伸缩、日志和监控等)。借助 Serverless…

APT攻击及密码学

目录标题什么是APT攻击?APT攻击过程APT防御技术APT防御过程沙箱处理流程密码学什么是对称加密?什么是非对称加密?SSL工作过程什么是APT攻击? APT攻击即高级可持续威胁攻击,也称为定向威胁攻击,指某组织对特…

fastCGI快速上手

fastCGI OVERVIEWfastCGI一、CGI二、fastCGI三、fastCGI使用1.fastCGI和spawn-fcgi安装2.nginx fastcgi(1)nginx的数据转发(2)spawn-fcgi启动(3)fastCGI进程处理3.fastCGI总结四、其他1.fastCGI环境变量2.…

【图像分割】LabelMe基本使用/标注标签格式转换及可视化

前言 之前一直在做目标检测的相关内容,使用LabelImg标注检测数据轻车熟路。不过最近尝试探索一下图像分割场景,需要用到LabelMe标注用于分割的数据标签,本文进行过程记录。 图像分割数据标签示例 以道路分割为例,下图是deepglo…

基于MVC+SSH的文章发布系统源码数据库毕业论文

目 录 摘要 1 Abstract 2 1 绪论 1.1 课题背景 1.2 研究现状及发展趋势 1.3 课题意义与目的 1.4 研究内容 1.5 论文组织 2 开发工具和技术介绍 2.1 开发工具 2.1.1 Myeclipse简介 2.1.2 MySQL简介 2.1.3 Tomcat简介 2.2 开发技术 2.…

【LInux】MySQL高可用之主从复制

一、Mysql主从架构技术说明 Mysql内建的复制功能是构建大型,高性能应用程序的基础。将Mysql的数据分布到 多个系统上去,这种分布的机制,是通过将Mysql的某一台主机(Master)的数据复 制到其它主(slaves&…

音频模块的介绍

一、术语总结 1.HIFI 级   “HIFI”一词通常指高保真音频(High-Fidelity Audio),是指尽可能保持音频信号的原始质量,让听众感受到最真实的音乐表现。因此,“HIFI级”通常指具有高保真音频性能的产品或设备&#xff…

音视频开发十六:SDL基础概念

SDL 纹理渲染 纹理 ​ 在SDL中,纹理是图像的描述信息。用SDL_Texture表示一个纹理对象(texture),它是一个用于存储像素数据的结构体类型。 渲染 ​ **互联网解释:**渲染是在电脑绘图中是指用软件从模型生成图像的…

【Linux】线程互斥详解:多线程会有什么问题?什么是互斥锁?C++怎么封装使用互斥锁?

多线程可以提高程序的并发性和运行效率,充分利用计算机的多核资源. 前面的几篇文章已经介绍了, Linux线程的基本概念、基本控制等内容. 我们已经看到了多线程可以提升运行效率等. 但是, 也发现了问题, 多线程可能会导致输出混乱、访问共享资源混乱、竞争等问题. 输…

Vue中 import ...与import{ }、import from ‘@路径‘ 与 import from ‘../路径‘

遇到的问题: 不带{ }以组件方式 引入后,用 组件名. 变量 的方式一直提示变量未定义的问题,改成直接 带{ }引入变量直接使用变量就不提示错误了,(不知道是不是我引入的包和他人不一样的原因...)&#xff0…

至尊宝工具箱 - 电商运营选品必备工具下载安装详细教程

简介 至尊宝插件是一款电商网络浏览插件,能够帮助店主更好地运营自己的网店,这款插件功能十分全面强大,可以实现搜主图、快速商家商品、优化商品标题等,通过帮助店家增加曝光量来增效销售,感兴趣的朋友快来体验。 下…

使用Superlink控制物联网网关远程调试PLC示例(PLC/HMI调试工具)

目录 1.Superlink工具使用说明 1.1软件安装 1.2账号登录 1.3设备查看 1.4菜单栏使用 2. Superlink网关远程调试网口/串口设备示例 2.1 远程调试西门子网口1200 PLC示例 2.2 远程调试欧姆龙232 PLC实串口通讯示例 3. 常见使用问题及技术支持 3.1 Superlink工具使用问题…

Mybatis核心

文章目录前言一、Configuration二、MappedStatement三、SqlSession四、Executor五、StatementHandler六、ParameterHandler七、ResultSetHandler八、TypeHandler总结前言 SqlSession是MyBatis提供的面向用户的操作数据库API。那么MyBatis底层是如何工作的呢?为了解…

vue.js表情文本输入框组件

文章目录参考链接效果图代码emoji.jsonEmojiText.vue使用参考链接 JS操作文本域获取光标/指定位置插入 vue.js支持表情输入 ttkwsd博客 效果图 代码 不能换行的bug已处理… emoji.json 表情图片放在public的emoji文件夹下面 emoji.json放在src/components/EmojiText文件夹…

Linux 生成pem文件 用于免密登录

1.在远程机器生成.pem文件 生成密钥对: $ ssh-keygen -t rsa -b 2048 -v 直接确认下一步 查看生成的密钥对: $ ls ~/.ssh/ 将私钥重命名至id_rsa.pem: $ mv ~/.ssh/id_rsa ~/.ssh/id_rsa.pem 修改~/.ssh/目录权限: $ chmod …

力扣sql中等篇练习(三)

力扣sql中等篇练习(三) 1 树节点 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # not in匹配上了返回的值是1,casw when里面也是可以使用not in和子查询的 # 注意去重的时候需要筛选掉null值 SELECT id,case when p_id is null then Rootwhen (id …

暄桐好作业之《临王蒙〈具区林屋图〉》

告诉大家一个好消息“暄桐好作业”栏目上新啦~除了与大家分享正在进行的课程好作业,还会向大家展示来自暄桐学长学姐们的优秀国画作品。希望正在上课的暄桐同学们能够从学长学姐的分享以及暄桐教室专业助教的点评中,从中获益并获得力量,继续努…

Java基础(十七):日期时间API

Java基础系列文章 Java基础(一):语言概述 Java基础(二):原码、反码、补码及进制之间的运算 Java基础(三):数据类型与进制 Java基础(四):逻辑运算符和位运算符 Java基础(五):流程控制语句 Java基础(六)&#xff1…