Python装饰器带括号和不带括号的理解

装饰器是 Python 中一个强大且灵活的特性,允许用户在不修改原有函数或类定义的基础上,为其增加额外功能。

今天在尝试自定义 Python 装饰器的时候遇到了一个问题,因为以前一直是使用装饰器,基本没有自定义过装饰器,所以写了一个不是常见的写法(符合装饰器的语法,但是用法上是错误的)

一、Python 装饰器的简单介绍

装饰器本质上是一个可调用对象(通常是函数),它接受一个函数作为输入,并返回一个新的函数作为输出。这个新函数通常会在执行原始函数前后添加额外的操作,从而扩展或改变原始函数的行为。

Python 提供了简洁的语法糖来应用装饰器,即在函数定义之前使用 @my_decorator 的格式。例如:

@my_decorator
def my_function():pass

这里的 @my_decorator 实际上是一个语法糖,会将 my_function 传递给 my_decorator 函数,并将返回的结果重新绑定到 my_function 上。因此上诉调用等同于:

def my_function():passmy_function = my_decorator(my_function)

这里需要强调一下,是将 my_function 传递给 @ 后面的整个部分,可以在看完文章之后再返回来理解一下这句话。

二、不带括号和带括号的 Python 装饰器

然后我就写了一个装饰器,不过我犯了一个错误,现在我把代码整理了一下贴了出来。第一种是通常的写法,第二种是我的写法,不过它的调用会有问题。不过这里目前可以看出区别就是一个不带括号,另一个带括号。

import timedef log1(func):def wrapper1():start = time.time()res = func()print("exec time: %.2f" % (time.time()-start))return resprint("log1 wrapper1")return wrapper1@log1
def func1():time.sleep(0.15)print("call func1")def log2():def wrapper2(func):start = time.time()res = func()print("exec time: %.2f" % (time.time()-start))return resprint("log2 wrapper2")return wrapper2@log2()  # 不带括号会报错,提示不需要参数,但是接收到了一个参数
def func2():time.sleep(0.15)print("call func2")if __name__ == '__main__':func1()# 如果不使用装饰器,那么:# func1 的调用方式等价于  func1 => log1(func1),这还是一个函数# func2 的调用方式等价于 func2 => log2()(func2),这不是一个函数,而是一个结果了

在这里插入图片描述

如果不使用装饰器语法,而是普通的 Python 代码的方式是这样的(结果同上):

import timedef log1(func):def wrapper1():start = time.time()res = func()print("exec time: %.2f" % (time.time()-start))return resprint("log1 wrapper1")return wrapper1def func1():time.sleep(0.15)print("call func1")def log2():def wrapper2(func):start = time.time()res = func()print("exec time: %.2f" % (time.time()-start))return resprint("log2 wrapper2")return wrapper2def func2():time.sleep(0.15)print("call func2")if __name__ == '__main__':func1 = log1(func1)func1()log2()(func2)

对于装饰器用法的代码,虽然没有调用 func2(),但是它就会直接执行,而且调用 func2() 会报错。

@log2()  # 不带括号会报错,提示不需要参数,但是接收到了一个参数
def func2():time.sleep(0.15)print("call func2")

在这里插入图片描述

不过这个错误反而提醒了我,装饰器的原理应该是:在程序运行时,会将被装饰的函数作为参数传递给装饰器,也就是 @ 后的整个部分。

因此对于 @log1 来说,这就相当于:func1 = log1(func1),所以之后执行 func1 就是被装饰后的函数了(可以观察到打印输出的语句)。函数是直接传到了装饰器内部,这样比较容易理解。

对于 @log2() 来说,这就相当于:log2()(func2),函数是传到了 log2() 返回的函数中了(这里是 wrapper2),这样它的结果就不是一个函数了(非 callable),而是一个具体的值了(这里的结果为 None)。如果我使用 @log2,那么就是把函数传到 log2 中,但是这个函数是无参数的,如果给它传递参数就会报错了。参数是传递给它返回的函数中,所以装饰器需要加上括号调用,即 @log2()。不过这里的写法犯了一个错误,因此导致了导致了它直接执行了,因为它返回的不是一个函数,而是它的执行结果了。所以,解决的方式就是在内部再嵌套一层函数,修改之后的代码如下:

def log2():def wrapper2(func):def wrapper3():start = time.time()res = func()print("exec time: %.2f" % (time.time()-start))return resreturn wrapper3print("log2 wrapper2")return wrapper2@log2()
def func2():time.sleep(0.15)print("call func2")if __name__ == '__main__':func2()# 等价于 func2 = log2()(func2) 这里它还是一个函数(可调用),而不是一个返回值了()

在这里插入图片描述

我认为这里最大的问题就是这个装饰器的语法糖实在是太便利的,这样对于使用是非常方便的,但是凡事有好处就有坏处,它反而不利于我们对它的理解了。特别是,我之前有过 Java 的注解使用和学习经验。如果只是简单的使用确实不需要理解它是怎么工作的,但是对于想要深入理解的同学来说,还是需要去了解背后的运行机制。这里要把握的一点就是:在运行时,会将被装饰的函数作为参数传递给 @ 后面这整个部分,如果不带括号就是直接作为参数传输传入,然后返回一个新的函数。如果带括号,就是传递给它的返回值(内层函数,所以内层函数还要再嵌套才行,不然就是直接执行函数了)。所以即使被装饰的函数在内层,它的执行也是没有问题的,但你要明白这个过程!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用一下Chat-GPT写两段处理字符串的简单样例ABAP程序。这样可以大大提高工作效率。Chat-GPT的能力真是让人震撼。

我让Caht-GPT写两段ABAP 程序,第一段程序要求如下: 判读字符串里面是否含有特殊字符,这里说的特殊字符不包括键盘上能够输入的字符,如果有这样的特殊字符则输出来。 DATA: lv_string TYPE string VALUE 你的字符串,lv_result TYP…

【Go】Go Swagger 生成和转 openapi 3.0.3

本文档主要描述在 gin 框架下用 gin-swagger 生成 swagger.json 的内容,中间猜的坑。以及,如何把 swagger 2.0 转成 openapi 3.0.3 下面操作均在项目根目录下执行 生成 swagger 2.0 import swagger go get -u github.com/swaggo/gin-swagger go get …

【Java orm 框架比较】十一 新增 原生jdbc对比

迁移到(https://gitee.com/wujiawei1207537021/spring-orm-integration-compare) orm框架使用性能比较 比较mybatis-plus、lazy、sqltoy、mybatis-flex、easy-query、mybatis-mp、jpa、dbvisitor、beetlsql、dream_orm、wood、hammer_sql_db、原生jdbc…

Python图形界面(GUI)Tkinter笔记(四):控件的定位(2)

Tkinter(GUI)设计图形界面时有三种控件的包装方法去定位各控件在窗口(父容器、根窗口)上的位置。 【1】pack()方法:用方位来定位位置,类似于Word文档中的文字对齐方式。 【2】grid()方法:用二…

计算机网络技术主要学什么内容,有哪些课程

计算机网络技术专业是一个涉及理论与实践紧密结合的学科,主要学习内容有计算机网络基础、网络设备技术、网络编程等内容,以下是上大学网(www.sdaxue.com)整理的计算机网络技术主要学什么内容,供大家参考! 基…

JVM 类加载机制

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.class 对…

QX----mini51单片机学习---(7)矩阵键盘

目录 1矩阵键盘的识别 2相关c语言 3实践编程 1矩阵键盘的识别 假设按列扫描按下S6P30:0P34:1然后高流向低,P34:0,刚开始是0xf0:1111 0000 后面是0xe0:1110 0000 ,当是0xe0能确…

了解 Swagger 中 allOf 的最佳实践

Swagger 提供了一个名为 allOf 的特性,它是通过扩展已有的数据模型来构造更为复杂的数据结构的有效手段。这一特性主要用于数据模型的继承及属性的组合,有效减少了代码重复,同时增强了代码的可维护性与清晰度。访问 Swagger 官方网站可以获得…

合专家模型 (MoE) 详解

本文转载自:混合专家模型 (MoE) 详解 https://huggingface.co/blog/zh/moe 英文版:https://huggingface.co/blog/moe 文章目录 一、简短总结二、什么是混合专家模型?三、混合专家模型简史四、什么是稀疏性?五、混合专家模型中令牌的负载均衡…

简洁大气APP下载单页源码

源码介绍 简洁大气APP下载单页源码,源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改,双击html文件可以本地运行效果,也可以上传到服务器里面 效果截图 源码下载 简洁大气APP下载单页源码

Hive Transaction事务表(含实现原理)

Hive Transaction事务表 在Hive中,事务表(Transactional Tables)允许用户执行事务性操作,包括ACID(原子性、一致性、隔离性、持久性)特性。事务表是在Hive 0.14版本引入的,并且在后续版本中不断…

【本地部署及云化部署】

文章目录 本地部署及云化部署介绍 文章目录 文章目录一、本地部署模式二、云化部署模式总结 一、本地部署模式 需建设专业化机房,系统应用、前端软件全部安装到本地服务器上。需要专业的IT、网络安全、DBA、电气化工程师进行维护。近些年勒索病毒安全事件频发&am…