python装饰器简介---这一篇也许就够了(推荐)


Posted in Python onApril 01, 2019

Python装饰器(decorator)是在程序开发中经常使用到的功能,合理使用装饰器,能让我们的程序如虎添翼。

装饰器引入

初期及问题诞生

假如现在在一个公司,有A B C三个业务部门,还有S一个基础服务部门,目前呢,S部门提供了两个函数,供其他部门调用,函数如下:

def f1():
  print('f1 called')


def f2():
  print('f2 called')

在初期,其他部门这样调用是没有问题的,随着公司业务的发展,现在S部门需要对函数调用假如权限验证,如果有权限的话,才能进行调用,否则调用失败。考虑一下,如果是我们,该怎么做呢?

方案集合

  1. 让调用方也就是ABC部门在调用的时候,先主动进行权限验证
  2. S部门在对外提供的函数中,首先进行权限认证,然后再进行真正的函数操作

问题

  1. 方案一,将本不该暴露给外层的权限认证,暴露在使用方面前,同时如果有多个部门呢,要每个部门每个人都要周知到,你还不缺定别人一定会这么做,不靠谱。。。
  2. 方案二,看似看行,可是当S部门对外提供更多的需要进行权限验证方法时,每个函数都要调用权限验证,同样也实在费劲,不利于代码的维护性和扩展性

那么,有没有一种方法能够遵循代码的开放闭合原则,来完美的解决此问题呢?

装饰器引入

答案肯定是有的,不然真的是弱爆了。先看代码

def w1(func):
  def inner():
    print('...验证权限...')
    func()

  return inner


@w1
def f1():
  print('f1 called')


@w1
def f2():
  print('f2 called')


f1()
f2()

输出结果为

...验证权限...
f1 called
...验证权限...
f2 called

可以通过代码及输出看到,在调用f1 f2 函数时,成功进行了权限验证,那么是怎么做到的呢?其实这里就使用到了装饰器,通过定义一个闭包函数w1,在我们调用函数上通过关键词@w1,这样就对f1 f2函数完成了装饰。

装饰器原理

首先,开看我们的装饰器函数w1,该函数接收一个参数func,其实就是接收一个方法名,w1内部又定义一个函数inner,在inner函数中增加权限校验,并在验证完权限后调用传进来的参数func,同时w1的返回值为内部函数inner,其实就是一个闭包函数。

然后,再来看一下,在f1上增加@w1,那这是什么意思呢?当python解释器执行到这句话的时候,会去调用w1函数,同时将被装饰的函数名作为参数传入(此时为f1),根据闭包一文分析,在执行w1函数的时候,此时直接把inner函数返回了,同时把它赋值给f1,此时的f1已经不是未加装饰时的f1了,而是指向了w1.inner函数地址。

接下来,在调用f1()的时候,其实调用的是w1.inner函数,那么此时就会先执行权限验证,然后再调用原来的f1(),该处的f1就是通过装饰传进来的参数f1。

这样下来,就完成了对f1的装饰,实现了权限验证。

装饰器知识点

执行时机

了解了装饰器的原理后,那么它的执行时机是什么样呢,接下来就来看一下。
国际惯例,先上代码

def w1(fun):
  print('...装饰器开始装饰...')

  def inner():
    print('...验证权限...')
    fun()

  return inner


@w1
def test():
  print('test')

test()

输出结果为

...装饰器开始装饰...
...验证权限...
test

由此可以发现,当python解释器执行到@w1时,就开始进行装饰了,相当于执行了如下代码:

test = w1(test)

两个装饰器执行流程和装饰结果

当有两个或两个以上装饰器装饰一个函数时,那么执行流程和装饰结果是什么样的呢?同样,还是以代码来说明问题。

def makeBold(fun):
  print('----a----')

  def inner():
    print('----1----')
    return '<b>' + fun() + '</b>'

  return inner


def makeItalic(fun):
  print('----b----')

  def inner():
    print('----2----')
    return '<i>' + fun() + '</i>'

  return inner


@makeBold
@makeItalic
def test():
  print('----c----')
  print('----3----')
  return 'hello python decorator'


ret = test()
print(ret)

输出结果:

----b----
----a----
----1----
----2----
----c----
----3----
<b><i>hello python decorator</i></b>

可以发现,先用第二个装饰器(makeItalic)进行装饰,接着再用第一个装饰器(makeBold)进行装饰,而在调用过程中,先执行第一个装饰器(makeBold),接着再执行第二个装饰器(makeItalic)。

为什么呢,分两步来分析一下。

  1. 装饰时机 通过上面装饰时机的介绍,我们可以知道,在执行到@makeBold的时候,需要对下面的函数进行装饰,此时解释器继续往下走,发现并不是一个函数名,而又是一个装饰器,这时候,@makeBold装饰器暂停执行,而接着执行接下来的装饰器@makeItalic,接着把test函数名传入装饰器函数,从而打印'b',在makeItalic装饰完后,此时的test指向makeItalic的inner函数地址,这时候有返回来执行@makeBold,接着把新test传入makeBold装饰器函数中,因此打印了'a'。
  2. 在调用test函数的时候,根据上述分析,此时test指向makeBold.inner函数,因此会先打印‘1‘,接下来,在调用fun()的时候,其实是调用的makeItalic.inner()函数,所以打印‘2‘,在makeItalic.inner中,调用的fun其实才是我们最原声的test函数,所以打印原test函数中的‘c‘,‘3‘,所以在一层层调完之后,打印的结果为<b><i>hello python decorator</i></b> 。

对无参函数进行装饰

上面例子中的f1 f2都是对无参函数的装饰,不再单独举例

对有参函数进行装饰

在使用中,有的函数可能会带有参数,那么这种如何处理呢?

代码优先:

def w_say(fun):
  """
  如果原函数有参数,那闭包函数必须保持参数个数一致,并且将参数传递给原方法
  """

  def inner(name):
    """
    如果被装饰的函数有行参,那么闭包函数必须有参数
    :param name:
    :return:
    """
    print('say inner called')
    fun(name)

  return inner


@w_say
def hello(name):
  print('hello ' + name)


hello('wangcai')

输出结果为:

say inner called
hello wangcai

具体说明代码注释已经有了,就不再单独说明了。
此时,也许你就会问了,那是一个参数的,如果多个或者不定长参数呢,该如何处理呢?看看下面的代码你就秒懂了。

def w_add(func):
  def inner(*args, **kwargs):
    print('add inner called')
    func(*args, **kwargs)

  return inner


@w_add
def add(a, b):
  print('%d + %d = %d' % (a, b, a + b))


@w_add
def add2(a, b, c):
  print('%d + %d + %d = %d' % (a, b, c, a + b + c))


add(2, 4)
add2(2, 4, 6)

输出结果为:

add inner called
2 + 4 = 6
add inner called
2 + 4 + 6 = 12

利用python的可变参数轻松实现装饰带参数的函数。

对带返回值的函数进行装饰

下面对有返回值的函数进行装饰,按照之前的写法,代码是这样的

def w_test(func):
  def inner():
    print('w_test inner called start')
    func()
    print('w_test inner called end')
  return inner


@w_test
def test():
  print('this is test fun')
  return 'hello'


ret = test()
print('ret value is %s' % ret)

输出结果为:

w_test inner called start
this is test fun
w_test inner called end
ret value is None

可以发现,此时,并没有输出test函数的‘hello',而是None,那是为什么呢,可以发现,在inner函数中对test进行了调用,但是没有接受不了返回值,也没有进行返回,那么默认就是None了,知道了原因,那么来修改一下代码:

def w_test(func):
  def inner():
    print('w_test inner called start')
    str = func()
    print('w_test inner called end')
    return str

  return inner


@w_test
def test():
  print('this is test fun')
  return 'hello'


ret = test()
print('ret value is %s' % ret)

输出结果:

w_test inner called start
this is test fun
w_test inner called end
ret value is hello

这样就达到预期,完成对带返回值参数的函数进行装饰。

带参数的装饰器

介绍了对带参数的函数和有返回值的函数进行装饰,那么有没有带参数的装饰器呢,如果有的话,又有什么用呢?
答案肯定是有的,接下来通过代码来看一下吧。

def func_args(pre='xiaoqiang'):
  def w_test_log(func):
    def inner():
      print('...记录日志...visitor is %s' % pre)
      func()

    return inner

  return w_test_log


# 带有参数的装饰器能够起到在运行时,有不同的功能

# 先执行func_args('wangcai'),返回w_test_log函数的引用
# @w_test_log
# 使用@w_test_log对test_log进行装饰
@func_args('wangcai')
def test_log():
  print('this is test log')


test_log()

输出结果为:

...记录日志...visitor is wangcai
this is test log

简单理解,带参数的装饰器就是在原闭包的基础上又加了一层闭包,通过外层函数func_args的返回值w_test_log就看出来了,具体执行流程在注释里已经说明了。
好处就是可以在运行时,针对不同的参数做不同的应用功能处理。

通用装饰器

介绍了这么多,在实际应用中,如果针对没个类别的函数都要写一个装饰器的话,估计就累死了,那么有没有通用万能装饰器呢,答案肯定是有的,废话不多说,直接上代码。

def w_test(func):
  def inner(*args, **kwargs):
    ret = func(*args, **kwargs)
    return ret

  return inner


@w_test
def test():
  print('test called')


@w_test
def test1():
  print('test1 called')
  return 'python'


@w_test
def test2(a):
  print('test2 called and value is %d ' % a)


test()
test1()
test2(9)

输出结果为:

test called
test1 called
test2 called and value is 9

把上面几种示例结合起来,就完成了通用装饰器的功能,原理都同上,就不过多废话了。

类装饰器

装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
在python中,一般callable对象都是函数,但是也有例外。比如只要某个对象重写了call方法,那么这个对象就是callable的。
当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行,但进行修改后,就可以直接执行调用了,如下

class Test(object):
  def __call__(self, *args, **kwargs):
    print('call called')


t = Test()
print(t())

输出为:

call called

下面,引入正题,看一下如何用类装饰函数。

class Test(object):
  def __init__(self, func):
    print('test init')
    print('func name is %s ' % func.__name__)
    self.__func = func

  def __call__(self, *args, **kwargs):
    print('装饰器中的功能')
    self.__func()


@Test
def test():
  print('this is test func')


test()

输出结果为:

test init
func name is test
装饰器中的功能
this is test func

和之前的原理一样,当python解释器执行到到@Test时,会把当前test函数作为参数传入Test对象,调用init方法,同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,其实就是直接对创建的对象进行调用,执行其call方法。

好了,到目前为止,基本把python装饰器及相关知识点讲完了,如有问题,欢迎指出哈~

以上所述是小编给大家介绍的python装饰器简介详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
利用Psyco提升Python运行速度
Dec 24 Python
浅谈Python处理PDF的方法
Nov 10 Python
Python编程给numpy矩阵添加一列方法示例
Dec 04 Python
Python+matplotlib+numpy绘制精美的条形统计图
Jan 02 Python
python中matplotlib的颜色及线条控制的示例
Mar 16 Python
python将文本中的空格替换为换行的方法
Mar 19 Python
Python DataFrame.groupby()聚合函数,分组级运算
Sep 18 Python
Python Pandas 箱线图的实现
Jul 23 Python
解决Python3用PIL的ImageFont输出中文乱码的问题
Aug 22 Python
如何使用python3获取当前路径及os.path.dirname的使用
Dec 13 Python
如何基于Python制作有道翻译小工具
Dec 16 Python
python logging 日志的级别调整方式
Feb 21 Python
Python批量删除只保留最近几天table的代码实例
Apr 01 #Python
Python中的Socket 与 ScoketServer 通信及遇到问题解决方法
Apr 01 #Python
python assert的用处示例详解
Apr 01 #Python
使用Python操作FTP实现上传和下载的方法
Apr 01 #Python
Python提取特定时间段内数据的方法实例
Apr 01 #Python
如何使用Python进行OCR识别图片中的文字
Apr 01 #Python
Python datetime和unix时间戳之间相互转换的讲解
Apr 01 #Python
You might like
php数组声明、遍历、数组全局变量使用小结
2013/06/05 PHP
php生成curl命令行的方法
2015/12/14 PHP
详解EventDispatcher事件分发组件
2016/12/25 PHP
PHP 计算至少是其他数字两倍的最大数的实现代码
2020/05/26 PHP
Javascript insertAfter() 实现函数代码
2011/10/12 Javascript
JavaScript生成随机数的4种自定义函数分享
2015/02/28 Javascript
jquery ajax后台返回list,前台用jquery遍历list的实现
2016/10/30 Javascript
jQuery快速实现商品数量加减的方法
2017/02/06 Javascript
关于vue-router的beforeEach无限循环的问题解决
2017/09/09 Javascript
详解Vue中watch对象内属性的方法
2019/02/01 Javascript
vue输入节流,避免实时请求接口的实例代码
2019/10/30 Javascript
快速了解Vue父子组件传值以及父调子方法、子调父方法
2020/07/15 Javascript
Element Collapse 折叠面板的使用方法
2020/07/26 Javascript
浅谈JavaScript中的“!!”作用
2020/08/03 Javascript
Python中用于计算对数的log()方法
2015/05/15 Python
利用Python实现微信找房机器人实例教程
2019/03/10 Python
python3通过selenium爬虫获取到dj商品的实例代码
2019/04/25 Python
Python命名空间namespace及作用域原理解析
2020/06/05 Python
Python装饰器结合递归原理解析
2020/07/02 Python
详解Python 循环嵌套
2020/07/09 Python
python+requests接口自动化框架的实现
2020/08/31 Python
Python tempfile模块生成临时文件和临时目录
2020/09/30 Python
python调用百度AI接口实现人流量统计
2021/02/03 Python
html5 http的轮询和Websocket原理
2018/10/19 HTML / CSS
SHEIN香港:价格实惠的女性时尚服装
2018/08/14 全球购物
linux面试题参考答案(6)
2016/06/23 面试题
毕业实习个人鉴定范文
2013/12/10 职场文书
网上卖盒饭创业计划书范文
2014/02/07 职场文书
酒店行政人事部经理职务说明书
2014/02/26 职场文书
关于环保的建议书
2014/05/12 职场文书
党的群众路线教育实践活动个人对照检查材料(乡镇)
2014/11/05 职场文书
国王的演讲观后感
2015/06/03 职场文书
2016年校园社会综合治理宣传月活动总结
2016/03/16 职场文书
在HTML5 localStorage中存储对象的示例代码
2021/04/21 Javascript
OpenCV-Python实现人脸美白算法的实例
2021/06/11 Python
python中tkinter复选框使用操作
2021/11/11 Python