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

装饰器的引入

初期及问题的诞生

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

deff1():print('f1 called')deff2():print('f2 called')

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

方案集合

1、让调用方也就是ABC部门在调用的时候,先主动进行权限验证

2、S部门在对外提供的函数中,首先进行权限认证,然后再进行真正的函数操作

问题

方案一,将本不该暴露给外层的权限认证,暴露在使用方面前,同时如果有多个部门呢,要每个部门每个人都要周知到,你还不缺定别人一定会这么做,不靠谱。。。

方案二,看似看行,可是当S部门对外提供更多的需要进行权限验证方法时,每个函数都要调用权限验证,同样也实在费劲,不利于代码的维护性和扩展性

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

装饰器引入

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

defw1(func):definner():print('...验证权限...')

func()returninner

@w1deff1():print('f1 called')

@w1deff2():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(f1)

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

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

装饰器知识点

执行时机

了解了装饰器的原理后,那么它的执行时机是什么样呢,接下来就来看一下。

国际惯例,先上代码

defw1(fun):print('...装饰器开始装饰...')definner():print('...验证权限...')

fun()returninner

@w1deftest():print('test')

test()

输出结果为

...装饰器开始装饰...

...验证权限...

test

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

test = w1(test)

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

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

defmakeBold(fun):print('----a----')definner():print('----1----')return '' + fun() + ''

returninnerdefmakeItalic(fun):print('----b----')definner():print('----2----')return '' + fun() + ''

returninner

@makeBold

@makeItalicdeftest():print('----c----')print('----3----')return 'hello python decorator'ret=test()print(ret)

输出结果:

----b----

----a----

----1----

----2----

----c----

----3----

hello python decorator

可以发现,先用第二个装饰器(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",所以在一层层调完之后,打印的结果为hello python decorator

对无参函数进行装饰

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

对有参数函数进行装饰

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

代码优先:

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

definner(name):"""如果被装饰的函数有行参,那么闭包函数必须有参数

:param name:

:return:"""

print('say inner called')

fun(name)returninner

@w_saydefhello(name):print('hello' +name)

hello('wangcai')

输出结果为:

say inner called

hello wangcai

具体说明代码注释已经有了,就不再单独说明了。

此时,也许你就会问了,那是一个参数的,如果多个或者不定长参数呢,该如何处理呢?看看下面的代码你就秒懂了。

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

func(*args, **kwargs)returninner

@w_adddefadd(a, b):print('%d + %d = %d' % (a, b, a +b))

@w_adddefadd2(a, b, c):print('%d + %d + %d = %d' % (a, b, c, a + b +c))

add(2, 4)

add2(2, 4, 6)

输出结果为:

add inner called2 + 4 = 6add inner called2 + 4 + 6 = 12

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

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

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

defw_test(func):definner():print('w_test inner called start')

func()print('w_test inner called end')returninner

@w_testdeftest():print('this is test fun')return 'hello'ret=test()print('ret value is %s' % ret)

输出结果为:

w_test inner called start

thisistest fun

w_test inner called end

ret valueis None

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

defw_test(func):definner():print('w_test inner called start')

str=func()print('w_test inner called end')returnstrreturninner

@w_testdeftest():print('this is test fun')return 'hello'ret=test()print('ret value is %s' % ret)

输出结果:

w_test inner called start

thisistest fun

w_test inner called end

ret valueis hello

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

带参数的装饰器

介绍了对带参数的函数和有返回值的函数进行装饰,那么有没有带参数的装饰器呢,如果有的话,又有什么用呢?

答案肯定是有的,接下来通过代码来看一下吧。

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

func()returninnerreturnw_test_log#带有参数的装饰器能够起到在运行时,有不同的功能#先执行func_args('wangcai'),返回w_test_log函数的引用#@w_test_log#使用@w_test_log对test_log进行装饰

@func_args('wangcai')deftest_log():print('this is test log')

test_log()

输出结果为:

...记录日志...visitor iswangcai

thisis test log

简单理解,带参数的装饰器就是在原闭包的基础上又加了一层闭包,通过外层函数func_args的返回值w_test_log就看出来了,具体执行流程在注释里已经说明了。

好处就是可以在运行时,针对不同的参数做不同的应用功能处理。

通用装饰器

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

defw_test(func):def inner(*args, **kwargs):

ret= func(*args, **kwargs)returnretreturninner

@w_testdeftest():print('test called')

@w_testdeftest1():print('test1 called')return 'python'@w_testdeftest2(a):print('test2 called and value is %d' %a)

test()

test1()

test2(9)

输出结果为:

test called

test1 called

test2 calledand value is 9

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

类装饰器

装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。

在python中,一般callable对象都是函数,但是也有例外。比如只要某个对象重写了call方法,那么这个对象就是callable的。

当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行,但进行修改后,就可以直接执行调用了,如下

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

t=Test()print(t())

输出为:

call called

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

classTest(object):def __init__(self, func):print('test init')print('func name is %s' % func.__name__)

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

self.__func()

@Testdeftest():print('this is test func')

test()

输出结果为:

test init

func nameistest

装饰器中的功能

thisis test func

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

预备知识

在了解wraps修饰器之前,我们首先要了解partial和update_wrapper这两个函数,因为在wraps的代码中,用到了这两个函数。

partial

首先说partial函数,在官方文档的描述中,这个函数的声明如下:functools.partial(func, *args, **keywords)。它的作用就是返回一个partial对象,当这个partial对象被调用的时候,就像通过func(*args, **kwargs)的形式来调用func函数一样。如果有额外的 位置参数(args) 或者 关键字参数(*kwargs) 被传给了这个partial对象,那它们也都会被传递给func函数,如果一个参数被多次传入,那么后面的值会覆盖前面的值。

个人感觉这个函数很像C++中的bind函数,都是把某个函数的某个参数固定,从而构造出一个新的函数来。比如下面这个例子:

from functools importpartialdefadd(x, y):return x+y#这里创造了一个新的函数add2,只接受一个整型参数,然后将这个参数统一加上2

add2 = partial(add, y=2)

add2(3) #这里将会输出5

这个函数是使用C而不是Python实现的,但是官方文档中给出了Python实现的代码,如下所示,大家可以进行参考:

def partial(func, *args, **keywords):def newfunc(*fargs, **fkeywords):

newkeywords=keywords.copy()

newkeywords.update(fkeywords)return func(*args, *fargs, **newkeywords)

newfunc.func=func

newfunc.args=args

newfunc.keywords=keywordsreturn newfunc

update_wrapper

接下来,我们再来聊一聊update_wrapper这个函数,顾名思义,这个函数就是用来更新修饰器函数的,具体更新些什么呢,我们可以直接把它的源码搬过来看一下:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')

WRAPPER_UPDATES= ('__dict__',)defupdate_wrapper(wrapper,

wrapped,

assigned=WRAPPER_ASSIGNMENTS,

updated=WRAPPER_UPDATES):for attr inassigned:try:

value=getattr(wrapped, attr)exceptAttributeError:pass

else:

setattr(wrapper, attr, value)for attr inupdated:

getattr(wrapper, attr).update(getattr(wrapped, attr, {}))

wrapper.__wrapped__ =wrappedreturn wrapper

大家可以发现,这个函数的作用就是从 被修饰的函数(wrapped) 中取出一些属性值来,赋值给 修饰器函数(wrapper) 。为什么要这么做呢,我们看下面这个例子。

自定义修饰器v1

首先我们写个自定义的修饰器,没有任何的功能,仅有文档字符串,如下所示:

defwrapper(f):def wrapper_function(*args, **kwargs):"""这个是修饰函数"""

return f(*args, **kwargs)returnwrapper_function

@wrapperdefwrapped():"""这个是被修饰的函数"""

print('wrapped')print(wrapped.__doc__) #输出`这个是修饰函数`

print(wrapped.__name__) #输出`wrapper_function`

从上面的例子我们可以看到,我想要获取wrapped这个被修饰函数的文档字符串,但是却获取成了wrapper_function的文档字符串,wrapped函数的名字也变成了wrapper_function函数的名字。这是因为给wrapped添加上@wrapper修饰器相当于执行了一句wrapped = wrapper(wrapped),执行完这条语句之后,wrapped函数就变成了wrapper_function函数。遇到这种情况该怎么办呢,首先我们可以手动地在wrapper函数中更改wrapper_function的__doc__和__name__属性,但聪明的你肯定也想到了,我们可以直接用update_wrapper函数来实现这个功能。

自定义修饰器v2

我们对上面定义的修饰器稍作修改,添加了一句update_wrapper(wrapper_function, f)。

from functools importupdate_wrapperdefwrapper(f):def wrapper_function(*args, **kwargs):"""这个是修饰函数"""

return f(*args, **kwargs)

update_wrapper(wrapper_function, f)#<< 添加了这条语句

returnwrapper_function

@wrapperdefwrapped():"""这个是被修饰的函数"""

print('wrapped')print(wrapped.__doc__) #输出`这个是被修饰的函数`

print(wrapped.__name__) #输出`wrapped`

此时我们可以发现,__doc__和__name__属性已经能够按我们预想的那样显示了,除此之外,update_wrapper函数也对__module__和__dict__等属性进行了更改和更新。

wraps修饰器

OK,至此,我们已经了解了partial和update_wrapper这两个函数的功能,接下来我们翻出wraps修饰器的源码:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')

WRAPPER_UPDATES= ('__dict__',)defwraps(wrapped,

assigned=WRAPPER_ASSIGNMENTS,

updated=WRAPPER_UPDATES):return partial(update_wrapper, wrapped=wrapped,

assigned=assigned, updated=updated)

没错,就是这么的简单,只有这么一句,我们可以看出,wraps函数其实就是一个修饰器版的update_wrapper函数,它的功能和update_wrapper是一模一样的。我们可以修改我们上面的自定义修饰器的例子,做出一个更方便阅读的版本。

自定义修饰器v3

from functools importwrapsdefwrapper(f):

@wraps(f)def wrapper_function(*args, **kwargs):"""这个是修饰函数"""

return f(*args, **kwargs)returnwrapper_function

@wrapperdefwrapped():"""这个是被修饰的函数"""

print('wrapped')print(wrapped.__doc__) #输出`这个是被修饰的函数`

print(wrapped.__name__) #输出`wrapped`

至此,我想大家应该明白wraps这个修饰器的作用了吧,就是将 被修饰的函数(wrapped) 的一些属性值赋值给 修饰器函数(wrapper) ,最终让属性的显示更符合我们的直觉。

参考链接

https://segmentfault.com/a/1190000009398663

https://blog.csdn.net/u010358168/article/details/77773199

python装饰器-python修饰器(装饰器)以及wraps相关推荐

  1. Python 闭包、单个装饰器、多个装饰器、装饰器修饰类、应用场景

    1. 闭包 在 Python 中,函数也可以作为参数.我们可以执行下面的代码: def func(a, b):return a + bprint(func) 我们直接输出函数名,而没有加括号.输出结果 ...

  2. Python基础15-函数闭包与装饰器

    目录 装饰器概念 装饰器的实现 修改被装饰函数的代码(非装饰器实现) 修改被装饰函数的调用方式(非装饰器实现) 装饰器的实现(不完整的实现level1) 装饰器实现的语法糖(不完整的实现level2) ...

  3. python 装饰器 参数-[Python]写个带参数的装饰器

    上篇文章 Python装饰器为什么难理解?从函数到装饰器一步一步介绍了Python装饰器的来由,不知你对装饰器理解了没有,强烈建议你自己动手写个装饰器应用到项目中加深理解.装饰器可以很简单,也可以很复 ...

  4. python装饰器-Python @函数装饰器及用法(超级详细)

    前面章节中,我们已经讲解了 Python 内置的 3 种函数装饰器,分别是 @staticmethod.@classmethod 和 @property,其中 staticmethod().class ...

  5. python三层装饰器-python中自带的三个装饰器的实现

    说到装饰器,就不得不说python自带的三个装饰器: 1.@property 将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @propert ...

  6. python装饰器-python装饰器

    #### 1.什么是装饰器 * 装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数: * 装饰器的功能在于对函数或类功能的增强,这是一种低耦合的功能增强: #### 2.装饰器特点 * ...

  7. python中的装饰器怎么运行_Python 装饰器入门(上)

    翻译前想说的话: 这是一篇介绍python装饰器的文章,对比之前看到的类似介绍装饰器的文章,个人认为无人可出其右,文章由浅到深,由函数介绍到装饰器的高级应用,每个介绍必有例子说明.文章太长,看完原文后 ...

  8. Python 函数(类)的装饰器与闭包

    2019独角兽企业重金招聘Python工程师标准>>> 装饰器是在函数调用上的修饰.这些修饰仅是当声明一个函数或者方法的时候,才会应用的额外调用. 装饰器的语法看起来像是这个样子: ...

  9. python求助神器_【python从零开始(被称之为神器的装饰器)】- 环球网校

    [摘要]假设今天我中了500万,我打算在北京买一个厕所,厕所的功能就是大小便,我觉得需要加点什么,给厕所改造一下,但是要保证厕所的功能,添加一面镜子,再来个热水器,装个喷头.一个三星级厕所打造成功.这 ...

最新文章

  1. python调用stanfordNLP的NER接口
  2. 如何在线把网站html生成xml文件_快速抓取网站信息工具
  3. Linux 命令之 unrar -- 解压 rar 格式的文件
  4. vue-cli部署ngixs_Vue-cli项目部署到Nginx
  5. C++学习——默认构造函数
  6. 机器学习笔记(二)---- 线性回归
  7. 提醒!赶快弃掉这个区块链平台!
  8. macfee怎么生成释放代码_批处理应用:使用FLASHGET检查Mcafee SuperDat更新分享
  9. C语言新手写扫雷攻略1
  10. DbEntry 开发实践:Wiki 系统(四)
  11. CS224N笔记——机器翻译和GRU以及LSTM
  12. Android 四大组件学习之Activity六
  13. 51单片机对GPS卫星信号的解码并通过LCD1602或12864显示
  14. Java编译出现不可映射字符
  15. 计算机组成与设计试题,计算机组成原理试题
  16. win10计算机的数字小键盘,让Win10登录时默认开启数字小键盘
  17. 【Matlab土壤分类】多类SVM土壤分类【含GUI源码 1398期】
  18. 计算机基础——Excel 2010
  19. python转义字符\r的使用
  20. Source Files 与 Resource Files 的区别

热门文章

  1. Linux 计算某文件夹下的所有文件的md5值
  2. 进程管理3--经典的进程同步问题
  3. RHEL/CentOS/Fedora各种源(EPEL、Remi、RPMForge、RPMFusion)配置
  4. ubuntu 下 SubLime Text2 使用之创建快捷方式
  5. image shadow
  6. C#3.0笔记(五)Lambda表达式
  7. DataGridView 里数据的动态明细 DataGridView GridView
  8. HTML字符实体(Character Entities),转义字符串(Escape Sequence)
  9. poj3581Sequence(后缀数组)
  10. python3最好的书籍推荐-推荐几本Python3相关书籍?最好分一下基础、进阶、高级...