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 相关文章推荐
Python表示矩阵的方法分析
May 26 Python
Python面向对象之类的内置attr属性示例
Dec 14 Python
Python提取频域特征知识点浅析
Mar 04 Python
python3+django2开发一个简单的人员管理系统过程详解
Jul 23 Python
Python基于OpenCV实现人脸检测并保存
Jul 23 Python
python文档字符串(函数使用说明)使用详解
Jul 30 Python
python并发编程多进程 互斥锁原理解析
Aug 20 Python
python计算n的阶乘的方法代码
Oct 25 Python
Python分类测试代码实例汇总
Jul 23 Python
Selenium之模拟登录铁路12306的示例代码
Jul 31 Python
python实现快速文件格式批量转换的方法
Oct 16 Python
Python数据分析之pandas函数详解
Apr 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
让你的网站首页自动选择语言转跳
2006/12/06 PHP
php判断字符以及字符串的包含方法属性
2008/08/30 PHP
php 判断访客是否为搜索引擎蜘蛛的函数代码
2011/07/29 PHP
PHP 快速排序算法详解
2014/11/10 PHP
php正则替换处理HTML页面的方法
2015/06/17 PHP
php实现当前页面点击下载文件的简单方法
2016/09/22 PHP
PHP中类的自动加载的方法
2017/03/17 PHP
PHP绕过open_basedir限制操作文件的方法
2018/06/10 PHP
Thinkphp5.0框架视图view的模板布局用法分析
2019/10/12 PHP
javascript实现数字验证码的简单实例
2014/02/10 Javascript
使用script的src实现跨域和类似ajax效果
2014/11/10 Javascript
简介JavaScript中Boolean.toSource()方法的使用
2015/06/05 Javascript
Bootstrap 组件之按钮(二)
2016/05/11 Javascript
JavaScript的模块化开发框架Sea.js上手指南
2016/05/12 Javascript
AngularJS过滤器filter用法总结
2016/12/13 Javascript
Nuxt.js实现校验访问浏览器类型的中间件
2018/08/24 Javascript
基于Angular中ng-controller父子级嵌套的相关属性详解
2018/10/08 Javascript
详解vue中的computed的this指向问题
2018/12/05 Javascript
ES6 Map结构的应用实例分析
2019/06/26 Javascript
vue使用Sass时报错问题的解决方法
2020/10/14 Javascript
利用Python为iOS10生成图标和截屏
2016/09/24 Python
python算法表示概念扫盲教程
2017/04/13 Python
Python cookbook(数据结构与算法)实现优先级队列的方法示例
2018/02/18 Python
python中yield的用法详解——最简单,最清晰的解释
2019/04/04 Python
一行python实现树形结构的方法
2019/08/09 Python
CSS3混合模式mix-blend-mode/background-blend-mode简介
2018/03/15 HTML / CSS
Original Penguin美国官网:布拉德皮特、强尼德普喜爱的服装品牌
2016/10/25 全球购物
小学生检讨书大全
2014/02/06 职场文书
领导干部群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
公司领导班子群众路线四风问题对照检查材料
2014/10/02 职场文书
群众路线专项整治工作情况报告
2014/10/28 职场文书
投标承诺函格式
2015/01/21 职场文书
建筑质检员岗位职责
2015/04/08 职场文书
2015年档案室工作总结
2015/05/23 职场文书
优秀范文:《但愿人长久》教学反思3篇
2019/10/24 职场文书
Python还能这么玩之只用30行代码从excel提取个人值班表
2021/06/05 Python