python装饰器传参与不传参_Python装饰器不会传参?别着急,这篇文章为你解惑-程序员宅基地

技术标签: python装饰器传参与不传参  

今天是Python专题的第13篇文章,上一篇文章当中我们介绍了Python装饰器的定义和基本的用法,这篇文章我们一起来学习一下Python装饰器的一些进阶使用方法。对装饰器不太熟悉,或者错过了上篇内容的小伙伴可以点击下方传送门。

之前的文章当中我们从前到后仔细推到了一下装饰器的本质和用途,也学会了它的基本用法,已经足够应付80%的场景了。但是总有20%的场景使用基本的方法解决不了,这个时候就需要我们学习更多、更全的其他用法。

比如我想要通过一个参数控制装饰器的功能,这个问题其实很常见。就拿记录时间来说,我们都知道时间可以记录成很多种格式,比如可以记成2020-05-04也可以记录成20200504,还可以记录成04/05/2020,如果是后端还会记录时间的时间戳。比如说我们现在实现了一个记录日志的装饰器,用来给我们的方法打上日志,现在我们想要控制记录日志的时候打印出来的时间格式,这个需求使用最简单的装饰器就没有办法解决了。

这个时候,如果想要解决问题,就必须引入参数,也就是说我们必须要在装饰器当中加入参数才行。但问题来了,这个参数怎么加,加在哪里呢?

定义装饰器参数

在我们介绍具体的用法之前,我们先来回顾一下装饰器的代码:

def mydec(func):

@wraps(func)

def mywrap(*args, **kw):

print('hello this is decorator1')

func(*args, **kw)

return mywrap

@mydec

def helloWorld():

print('hello, world')

这个就是我们上次讲的最简单的那种装饰器,假如说我们这个时候希望传入一个参数type,可以控制装饰器的输出结果。就像这样:

@mydec(type_='test')

def helloWorld():

print('hello, world')

我们可能会想是不是应该在mydec这个方法的参数里面加上一个type_,但是如果你试一下就好发现这样是不行的,会得到一个error:

171e55015bb125c2?w=1412&h=304&f=jpeg&s=47072

Error错误的字面意思很好理解,但是原因却令人费解。这个Error是说函数mydec少了一个必选参数func,这个func就是我们要包装的函数,但是这个不是自动传入的吗,怎么会提示我们少了这个参数呢?

如果这个问题的本质不能理解的话,那么装饰器就很难大成了,因为只有理解清楚了这一点,才能理解后面装饰器各种稀奇古怪的进阶用法。但是很坑爹的是,很多资料当中都只是简单地介绍了怎么用,很少会探究其中背后的原因,这会让初学者在学习的时候陷入费解。我在学习的时候也花了很多心思,才终于搞明白,说穿了很简单,但是想通不容易。

其实这样会报错的主要原因是注解当中有参数和没有参数的装饰器是完全不同的。

我们来回顾一下不加参数的装饰器的用法,比如:

@mydec

def hello_world():

pass

我们执行hello_world()的时候,等价于执行mydec(hello_world)()。看明白了吗,我们把这行代码展开,它其实是下面这两行代码共同执行的结果:

cur = mydec(hello_world)

cur()

如果hello_world这个函数带上参数呢?

@mydec

def hello_world(*args, **kw):

pass

那么执行的时候它其实是这样的:

cur = mydec(hello_world)

cur(*args, **kw)

这个理解了之后,我们继续往下,现在我们想要将一个参数传给装饰器,按照我们的想法下面这两段代码应该是一样的。

@mydec(type_='test')

def helloWorld():

print('hello, world')

cur = mydec(hello_world, type_)

cur()

但是很遗憾的是,Python解释器当中并不是这么设计的。它对加上了参数的装饰器多做了一层封装,也就是说上面传入参数的hello_world函数执行的时候等价于下面这段代码:

cur1 = mydec(type_)

cur2 = cur1(hello_world)

cur2()

正是因为额外多封装了一层,所以函数和装饰器的参数传入装饰器的顺序是不同的,顺序也是不一样的。明白了这点之后就简单很多了,既然Python解释器在解释装饰器参数的时候多增加了一层,那么如果我们想要实现带参数的装饰器,只需要也在装饰器当中多封装一层就可以了。比如可以写成这样:

def mydec(type_=None):

def decorate(func):

@wraps(func)

def mywrap():

if type_ is not None:

print(type_)

func()

return mywrap

return decorate

这样我们再执行就可以了:

171e55015f932b16?w=1104&h=280&f=jpeg&s=19591

默认参数怎么办

到这里看似一切都很完美,但其实有一个很大的问题被我们忽略了。

这个问题就是默认参数问题,在前面我们定义装饰器的时候,将type_这个参数设置成了可选的。这也很符合我们实际情况,如非必要,参数能省略就省略。但是这就导致了一个问题,对于不用加上参数的装饰器,有些人习惯写成mydec(),有些人习惯写成mydec。如果我们试一下mydec,就会发现这样写会报错:

171e55015b953b36?w=1406&h=242&f=jpeg&s=38549

这个报错和上面的报错一模一样,出现的原因也是一样的,都是少了func参数。但是很奇怪啊,为什么会少了func呢?

原因很简单,因为我们把括号去掉,装饰器又回到了之前的两层结构!

cur = mydec(hello_world)

cur(*args, **kw)

这就很坑爹了,我们装饰器的结构肯定是不能改变的,如果使用两层结构就没办法传入参数了,但是如果不传参的时候怎么办,难道就只能强制程序员统一风格全部加上括号吗?这当然也是一个办法,那还有没有更好的办法呢?有没有办法统一这两种逻辑呢?

当然是有的,为了解决这个问题,我们需要用到一个新的工具,叫做偏函数。

偏函数很好理解,它本意也是一个高阶函数,其实就是闭包。偏函数的使用场景针对多参数的函数,通过使用偏函数,可以固定若干个参数的传值,从而起到简化函数传参的作用。我们来看一个例子,我们创建一个pow函数,用来计算x的n次方:

import math

def pow(x, n):

return math.pow(x, n)

这个函数需要传入x和n两个参数,如果我们当前只需要计算平方,我们可以使用闭包,固定其中的参数n,生成一个新的函数来做到这点。比如:

def mypow(n):

def func(x):

return pow(x, n)

return func

171e55015fda602e?w=892&h=156&f=jpeg&s=9172

偏函数的本质就是这样一个闭包,只不过它简化了我们的代码而已:

from functools import partial

pow2 = partial(pow, n=2)

pow2(6)

使用偏函数我们只需要传入待加工的原函数,以及固定的参数值即可。我们把偏函数用在装饰器当中,就可以解决刚才的问题。回忆一下,不带参数的装饰器是两层函数嵌套,而带上参数的是三层嵌套。那么我们使用partial,专门为带上参数的情况额外增加一层嵌套即可:

def mydec(func=None, type_=None):

# 不带参数的话,func会是None,这时候我们固定参数即可

if func is None:

return partial(mydec, type_=type_)

@wraps(func)

def mywrap():

if type_ is not None:

print(type_)

func()

return mywrap

我们来看下这其中的细节,当我们不传入参数的时候,我们其实执行的是cur = mydec(func),这个时候func不为空,那么不会触发if中的语句,所以会直接返回mywrap。如果传入参数,这时候func是None,会触发if中的partial。注意这里我们在partial当中传入的函数依然是mydec,也就是说我们固定了type_这个参数,调用的话依然返回的是mywrap,相当于我们通过partial额外在两层结构当中专门为带参数的情况增加了一层,统一了逻辑。

结尾

今天的概念比之前的装饰器要复杂很多,一时可能并不好理解,其实这是非常正常的。这不仅仅是装饰器的问题,也不仅是Python的问题,归根结底这是函数式编程的特性导致的。函数式编程的优点就是高度灵活,使用非常方便,但缺点也很明显,代码难以维护,阅读难度高,理解起来也不简单。典型的初学简单,精深非常难的典型。所以如果大家觉得一时理解不了,这并不是你们的问题,一方面我们需要培养自己函数传编程的思维,另一方面我们也需要熟悉Python中装饰器的使用方法。

最后说点题外话,由于只狼和仁王,最近有点迷上了硬核游戏。刚开始玩的时候,觉得非常困难,经常卡关,一个boss死个几十次是家常便饭。等到了后来,慢慢找到了诀窍,瞬间发现这类游戏甚至所有游戏都变得简单了。

这不仅仅是我熟悉了,更多的是因为玩游戏的时候也开始思考了,开始思考这些boss设计了哪些招数?设计者给我们留下了哪些操作的空间对付它?有哪些规律可循?思考的多了,诀窍也就有了。打多了之后,很多boss就只剩下了初见难,只要打个两三次熟悉了套路,就可以过关了。慢慢地我发现生活当中的很多事情其实和游戏中的boss一样,只是初见难,第一次见到的时候觉得无从下手,觉得难以理解,觉得庞然大物,所以很难。但只要有一颗坚毅、勇敢的心,学会冷静理智去分析,其实不过只是纸老虎而已。

希望能给大家一点小小的启发,希望大家面前的困难都只是纸老虎,希望大家都能找到自己的勇气。

今天的文章就到这里,原创不易,需要你的一个关注,你的举手之劳对我来说很重要。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39847945/article/details/110338805

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法