python装饰器深入学习


Posted in Python onApril 06, 2018

什么是装饰器

在我们的软件产品升级时,常常需要给各个函数新增功能,而在我们的软件产品中,相同的函数可能会被调用上百次,这种情况是很常见的,如果我们一个个的修改,那我们的码农岂不要挂掉了(有人就说了 ,你笨呀,修改函数定义不就行了!同学,你醒醒吧,如果要新加的功能会修改参数,或者返回值呢?)。这个时候,就是我们装饰器大显神通的时候了。装饰器就可以实现,在不改变原函数的调用形式下(即函数的透明化处理),给函数新增功能的作用。如何实现,以及实现原理,下文会详解。

装饰器遵循的原则

装饰器,顾名思义就是起装饰的作用,既然是装饰,那么被装饰的对象是啥样就是啥样,不能有丝毫改变。在这里,我们写装饰器就是必须把握不能修改被修饰函数的源代码这条铁律。如何遵循这条铁律,我们还需还需做一些铺垫,必须先要了解三个概念,如下:

函数名即“变量”

在python中,函数名其实就像是c语言的函数指针,代表的是我们的函数地址,只有解释器获取到这个地址,它才会去执行这块内存的代码。因此,本质上,函数名就和不同变量没什么区别,只不过函数名和普通变量所指代的那块内存的使用方式不同罢了,这些都是底层解释器的机制所决定的,对于程序猿来说,都是透明的,所以,我们可以认为两者是没有区别的。

高阶函数

什么是高阶函数其实很简单,把握两个原则就好:

  • 形式参数有函数名
  • 返回值有函数名

只要满足这两个原则之一,就可以称之为是高阶函数。翻回头来看,这里出现了我们上面说的函数名,仔细体会一下,我们在这里不就是把其当成实参看待的吗?

嵌套函

什么是嵌套函数其实也非常简单,把握一个原则就好:

  • 在一个函数的函数体中去定义另一个函数

在这里需要强调的是,函数定义时是不会执行函数体的,就和定义变量是不会去读取变量里的内容一样。这一点至关重要,对于我们理解装饰器实现原理非常有帮助。

如何写装饰器

有了上文的铺垫,在现在来详解一下如何写装饰器,就好理解多了。

装饰器本质

其实装饰器本质上就是一个函数,它也具有函数名,参数和返回值。但在python中,我们用“@auth”来表示。

@auth # 其等价于:func = auth(func)
def func():
 print("func called")

 这个示例就是python中如何修饰func函数的格式,当然我们还没有实现我们的装饰器函数。我们要注意的是注释里写的内容,我们可以看出:

  • 装饰器函数其实是一个高阶函数(参数和返回值都为函数名)。
  • “auth(func)”是在调用我们的装饰器函数,即装饰器函数的函数体会被执行,一定要记好这一点。

设计思路

装饰器即然是个函数,又有上述介绍的等价关系,那我们就可以这样设计我们的装饰器:

  • 在我们装饰器的函数体内去定义一个新的函数,在这个新定义的函数内去调用被修饰的函数,与此同时,在被修饰的函数的上下文去添加新功能。最后,利用装饰器函数的返回值返回我们新定义函数的函数名。
  • 由此可以知道,“func = auth(func)”中的返回值func表示的就是在装饰器中新定义的函数的函数名。

前面做了大量的铺垫,就是想在这里揭示装饰器的实现机制,其实没什么什么的,很简单:

  • 装饰器机制改变了被修饰函数的函数名表示的地址数据。说白了就是,被修饰前,函数名代表的是A内存块;被修饰后,函数名代表的是B内存块;只不过,在执行B内存块时,会调用A内存块罢了。B内存块中的代码就是我们新加的功能。而这种机制的实现,使用了“高阶函数”和“嵌套函数”的机制。
  • 最终的效果就是,但在调用被修饰过的函数时,其实调用的不是原来的内存块,而是修饰器新申请的内存块。

第一步:设计装饰器函数

装饰器函数定义跟普通函数定义没什么区别,关键是函数体怎么写的问题。这里,为了便于理解,先用无参数的装饰器函数说明。

#装饰器函数定义格式
def deco(func):
 '''函数体...'''
return func

这里说的无参数,指的是没有除了“func”之外的参数
难点是函数体的编写,下面的示例先告诉你为什么要有第二步:

#使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)”
def deco(func):
 print("before myfunc() called.")
 func()
 print("after myfunc() called.")
 return func
 
@deco
def myfunc():
 print("myfunc() called.")
 
myfunc()
myfunc()
 
#output:
before myfunc() called.
myfunc() called.
after myfunc() called.
myfunc() called.
myfunc() called.

由输出结果可以看出,我们的装饰器并没有生效。别跟我说装饰器只生效了一次,那是大家忽略了“@deco”的等效机制。解释到“@deco”时,会解释成“myfunc = deco(myfunc)”。注意了,前面我提到了,这里其实在调用deco函数的,因此,deco的函数体会被执行。所以output的前三行并不是调用myfunc函数时产生的效果,那有怎能说装饰器生效了一次呢?第二步就是解决装饰器没生效的问题的。

第二步:包装被修饰函数

#基本格式
def deco(func):
 def _deco()
 #新增功能
 #...
 #...
 func() #别修饰函数调用
 return_deco

 下面给出个示例:

#使用内嵌包装函数来确保每次新函数都被调用,
#内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象
 
def deco(func):
 def _deco():
 print("before myfunc() called.")
 func()
 print("after myfunc() called.")
 # 不需要返回func,实际上应返回原函数的返回值
 return _deco
 
@deco
def myfunc():
 print("myfunc() called.")
 return 'ok'
 
myfunc()
 
#output:
before myfunc() called.
myfunc() called.
after myfunc() called.

  第三步:被修饰函数参数和返回值透明化处理

当完成了第二步时,其实装饰器已经完成了主要部分,下面就是对被修饰函数的参数和返回值的处理。这样才能真正实现装饰器的铁律。话不多说,直接上代码:

#基本格式
def deco(func):
 def _deco(*args, **kwargs) #参数透明化
 #新增功能
 #...
 #...
 res = func(*args, **kwargs) #别修饰函数调用
 return res #返回值透明化
 return_deco

通过上面的分析知:

参数透明化:当我们在调用被装饰后的函数时,其实调用的时这里的_deco函数。那么,我们就给_deco函数加上可变参数,并把得到的可变参数传递给func函数不就可以了。
返回值透明化:和参数透明化同理,给_deco函数定义返回值,并返回func的返回值就可以了。

透明化处理就是这么简单!至此,我们的装饰器编写完成。给个示例吧:

#对带参数的函数进行装饰,
#内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象
 
def deco(func):
 def _deco(*agrs, **kwagrs):
 print("before myfunc() called.")
 ret = func(*agrs, **kwagrs)
 print(" after myfunc() called. result: %s" % ret)
 return ret
 return _deco
 
@deco
def myfunc(a, b):
 print(" myfunc(%s,%s) called." % (a, b))
 return a + b
 
print("sum=",myfunc(1, 2))
print("sum=",myfunc(3, 4))
 
#output:
before myfunc() called.
 myfunc(1,2) called.
 after myfunc() called. result: 3
sum= 3
before myfunc() called.
 myfunc(3,4) called.
 after myfunc() called. result: 7
sum= 7

装饰器进阶

带参数装饰器

装饰器即然也是函数,那么我们也可以给其传递参数。我这里说的是:“@auth(auth_type = 'type1')”这中形式哟。先上个代码吧:

#基本格式
def deco(deco_type)
 def _deco(func):
 def __deco(*args, **kwargs) #参数透明化
  #新增功能
  #...
  #...
  print("deco_type:",deco_type) #使用装饰器参数
  res = func(*args, **kwargs) #别修饰函数调用
  return res #返回值透明化
 return __deco
 return_deco

 说白了,就是在原来的装饰器的基础上再在最外层套一个deco函数,并用其来接收装饰器参数。由于是在最外层套了一个函数,那么这个函数的形参的作用范围就是函数体内部,所以里面的函数定义中随便用啦,就这么任性。
那怎么理解解释器的解析过程呢?在这里,只要我们明白一点就好,那就是: “@auth(auth_type = 'type1')”等价于“func = auth(auth_type = 'type1')(func)” 解释器会先翻译“auth(auth_type = 'type1')”,再将其返回值(假设给了_func这个不存在的函数名)当作函数指针,这里的_func函数名代表的是_deco,然后再去执行“func = _func(func)”,而这个func函数名代表的其实就是__deco。

至此,就达到了通过装饰器来传参的目的。给个示例吧:

#示例7: 在示例4的基础上,让装饰器带参数,
#和上一示例相比在外层多了一层包装。
#装饰函数名实际上应更有意义些
 
def deco(deco_type):
 def _deco(func):
 def __deco(*args, **kwagrs):
  print("before %s called [%s]." % (func.__name__, deco_type))
  func(*args, **kwagrs)
  print(" after %s called [%s]." % (func.__name__, deco_type))
 return __deco
 return _deco
 
@deco("mymodule")
def myfunc():
 print(" myfunc() called.")
 
@deco("module2")
def myfunc2():
 print(" myfunc2() called.")
 
myfunc()
myfunc2()
 
#output:
before myfunc called [mymodule].
 myfunc() called.
 after myfunc called [mymodule].
before myfunc2 called [module2].
 myfunc2() called.
 after myfunc2 called [module2].

多重装饰器修饰函数

如果说,我上面说的内容都理解了,那么这个东东,就太简单不过了。不就是把我们的是装饰器当中被修饰的函数,对它进行装饰吗?但我在这里还想说的是,我们换个角度看问题。我们的关注点放在原来的被修饰的函数上,就会发现,NB呀,我可以给它添加若干个功能撒。给个示例吧:

def deco(deco_type):
 def _deco(func):
 def __deco(*args, **kwagrs):
  print("before %s called [%s]." % (func.__name__, deco_type))
  func(*args, **kwagrs)
  print(" after %s called [%s]." % (func.__name__, deco_type))
 return __deco
 return _deco
 
@deco("module1")
@deco("mymodule")
def myfunc():
 print(" myfunc() called.")
 
@deco("module2")
def myfunc2():
 print(" myfunc2() called.")
 
myfunc()
 
#output:
before __deco called [module1].
before myfunc called [mymodule].
 myfunc() called.
 after myfunc called [mymodule].
 after __deco called [module1].

 注意结果哟,@deco("module1"),来修饰的deco("mymdule")的,和我们想的是一样的,完美!

领取干货:零基础入门学习python视频教程

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python生成指定尺寸缩略图的示例
May 07 Python
Python语言生成水仙花数代码示例
Dec 18 Python
python之pyqt5通过按钮改变Label的背景颜色方法
Jun 13 Python
PyQt5 加载图片和文本文件的实例
Jun 14 Python
浅谈python已知元素,获取元素索引(numpy,pandas)
Nov 26 Python
pandas factorize实现将字符串特征转化为数字特征
Dec 19 Python
Python xml、字典、json、类四种数据类型如何实现互相转换
May 27 Python
Python 高效编程技巧分享
Sep 10 Python
Python importlib模块重载使用方法详解
Oct 13 Python
Python爬虫之Selenium库的使用方法
Jan 03 Python
教你如何用Python实现人脸识别(含源代码)
Jun 23 Python
Python可变与不可变数据和深拷贝与浅拷贝
Apr 06 Python
如何使用 Pylint 来规范 Python 代码风格(来自IBM)
Apr 06 #Python
python中pylint使用方法(pylint代码检查)
Apr 06 #Python
VSCode下配置python调试运行环境的方法
Apr 06 #Python
如何使用VSCode愉快的写Python于调试配置步骤
Apr 06 #Python
详解django三种文件下载方式
Apr 06 #Python
Anaconda入门使用总结
Apr 05 #Python
致Python初学者 Anaconda入门使用指南完整版
Apr 05 #Python
You might like
判断PHP数组是否为空的代码
2011/09/08 PHP
php中判断文件空目录是否有读写权限的函数代码
2012/08/07 PHP
解决php表单重复提交实现方法
2015/09/29 PHP
PHP基于yii框架实现生成ICO图标
2015/11/13 PHP
Paypal实现循环扣款(订阅)功能
2017/03/23 PHP
laravel自定义分页效果
2017/07/23 PHP
thinkPHP框架实现的短信接口验证码功能示例
2018/06/20 PHP
前端开发的开始---基于面向对象的Ajax类
2010/09/17 Javascript
Js基础学习资料
2010/11/23 Javascript
jquery实现文本框textarea自适应高度
2016/03/09 Javascript
nodejs如何获取时间戳与时间差
2016/08/03 NodeJs
自动化测试读写64位操作系统的注册表
2016/08/15 Javascript
利用python分析access日志的方法
2016/10/26 Javascript
JS匿名函数实例分析
2016/11/26 Javascript
微信小程序 用户数据解密详细介绍
2017/01/09 Javascript
JavaScript 详解预编译原理
2017/01/22 Javascript
微信小程序 开发之全局配置
2017/05/05 Javascript
javascript中mouseenter与mouseover的异同
2017/06/06 Javascript
详解vuex中mapState,mapGetters,mapMutations,mapActions的作用
2018/04/13 Javascript
移动端如何用下拉刷新的方式实现上拉加载
2018/12/10 Javascript
layui的表单验证支持ajax判断用户名是否重复的实例
2019/09/06 Javascript
vue+Element-ui前端实现分页效果
2020/11/15 Javascript
vue3自定义dialog、modal组件的方法
2021/01/04 Vue.js
Python2.7版os.path.isdir中文路径返回false的解决方法
2019/06/21 Python
python里运用私有属性和方法总结
2019/07/08 Python
python opencv 简单阈值算法的实现
2019/08/04 Python
tensorflow实现在函数中用tf.Print输出中间值
2020/01/21 Python
顶丰TOPPIK台湾官网:增发纤维假发,告别秃发困扰
2018/06/13 全球购物
StubHub希腊:购买体育赛事、音乐会和剧院门票
2019/08/03 全球购物
Ruby如何实现动态方法调用
2012/11/18 面试题
地理科学专业毕业生求职信
2013/10/15 职场文书
农村婚庆司仪主持词
2014/03/15 职场文书
警示教育片观后感
2015/06/17 职场文书
2016反腐倡廉警示教育心得体会
2016/01/13 职场文书
Golang中interface{}转为数组的操作
2021/04/30 Golang
【海涛七七解说】DCG第二周:DK VS 天禄
2022/04/01 DOTA