定义
关于python中装饰器的定义,我们这里参考廖雪峰大神的python3教程中的定义:在某个函数(代码)运行期间,在不更改该函数的功能下,动态给该函数添加功能的方式,我们称之为“装饰器”。从定义中看出,这个装饰器势必要在实现中传入原函数,并在其功能中使原函数功能不受影响。由此我们想到的一种实现装饰器的方法就是:“实现一个功能(可是一段代码/函数),该功能的实现要传入一个函数。”翻译过来就是通过一个函数(比如叫“a”函数)实现装饰器的功能,但是该函数a的入参需要传入的是一个函数(比如叫“b”函数)。那么想象中的装饰器实现方式是:
# 这里的b是一个函数 def a(b): b() pass
而实际上python中基本的装饰器的模样是这样(注:文章中的大量定义和源码都参考廖雪峰大神的python教程):
# 这里的func是一个函数,与想象中的b函数一致,log就是上面中的a函数 def log(func): # *args,**kw泛指函数func的入参 def wrapper(*args, **kw): # __name__是python中的一个属性,它能返回对应函数的名称 print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
要使用装饰器,python中的方法如下:
# 给函数上面加一个@符号,再接实现了装饰器功能的函数名称 @log def now(): print('2015-3-25')
调用now()函数:
>>> now() call now(): 2015-3-25
可以看到使用了装饰器,给now函数动态增加了打印它的名称的功能。且没有影响它的原始功能。
根据对比,python中装饰器的实现跟想象中的实现方式差不多,不过python中实现装饰器的函数log其返回值是一个函数wrapper,这个log是一个 返回函数 ,什么是返回函数?为了弄清楚这个log函数,我们还是先来看一些函数的基础知识吧
基础知识
函数也是一种对象
在python中,“函数也是一种对象”,这句话是理解装饰器的基础。因为函数是一种对象,并且对象能被赋值给变量,所以函数就能被赋值给变量。例如:
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
这里now函数被赋值给了f变量,调用now函数,其实就是进行f的调用。平时来看,函数一般都有入参(即函数的局部变量),而因为变量能代表函数,故函数入参为函数也就好理解了。故对实现了装饰器的函数,它的入参为函数也就不奇怪了。在python中当一个函数a就将另一个函数b作为参数,那么这个函数a就称之为 高阶函数 。而上面的函数log将func函数作入参,那么log就是一个高阶函数。
返回函数
可以看到函数log它最终的返回值是一个函数wrapper。在python中,返回函数指:“对于一个函数c,它最后的返回值是一个函数d,那么函数c就是一个返回函数”。返回函数的好处就是当调用c函数时不用立即执行c中的所有逻辑,我们可以看下面的例子:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
函数lazy_sum实现了一个求和的功能,当我们调用lazy_sum时,其返回的不是直接的求和值,而是一个函数sum,
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
这里f变量其实就是sum函数,所以要求和的值时,调用f才可以。
>>> f()
25
“当有一个函数h,它定义了内部函数g,最后返回了函数g,并且在该过程中g还使用了h函数的局部变量,那么这种程序python中称之为 闭包 ”。根据闭包定义,其实它也是一种返回函数,只不过它的内部函数使用了外部函数的局部变量(入参)。
装饰器理解
装饰器剖析
有了前面的知识储备,再来看最开始实现了装饰器功能的函数log:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
可以看到log函数的入参为func函数,根据定义,log函数就是一个高阶函数。再看log函数的返回值为函数wrapper,根据定义,log函数也是一个返回函数。如果把func看作一个变量,那么内部的函数wrapper使用了外部函数log的变量,根据定义,这个log函数它的程序也是一个闭包。
此刻我们对于实现装饰器功能的程序就有了一个整体认识,它既是一个高阶函数也是一个返回函数。当然整体上看它也是一个闭包。
装饰器的运行过程
在python中,当我们使用装饰器:
@log
def now():
print('2015-3-25')
now()
now()运行过程,实际上相当于执行了语句:
now = log(now)
即 用了一个同原来now函数名字相同的变量now,(为了好区分,我们后面称变量now为now’吧。)来表示log函数的返回值。 这里log函数的返回值是函数wrapper,前面讲过函数是变量,变量能表示函数,此时now’变量表示了函数wrapper,即 now’ = wrapper ,now()的执行相当于执行了wrapper函数,即 now’() 。
表面上看加了装饰器的now函数,其调用跟不加装饰器正常调用now函数是一样的,但经过上面我分析实际上并并不是,它实际上执行的是 now’ = log(now); now’() 这两步。那句广告语“看起来一样一样,实际就是不一样…”,哈哈,说的就是这个过程。
带参数的装饰器
有时候我们给某个函数动态添加了功能,但又希望我们添加的功能对于我们的需求是可定制的。因此则有必要给装饰器添加参数变量。python中给装饰器添加参数的实现是这样的:
def log2(text):
def log(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return log
用法则如下:
@log2('execute')
def now():
print('2015-3-25')
>>> now()
execute now():
2015-3-25
直观上看,其实就是给log函数外部再加了一层函数。根据前面的函数定义,不难看出log2是一个返回函数。而now()的运行过程则相当于执行了下面的语句:
now = log2('execute')(now)
即用了一个同原来now函数名字相同的变量now,(为了好区分,我们也称变量now为now’吧。)来表示log函数的返回值。这里为什么now’表示的是log函数的返回值,而不是log2的返回值?相信聪明的你一眼也能得到答案哈!我们看,log2(‘execute’) 这个函数的执行,实际上是返回了log函数,我们假设将返回的log函数赋值给变量f,那么 log2(‘execute’)(now) 的执行其实就相当于 f(now) 的执行,而 f(now) 的执行,其返回值就是log函数的返回值即wrapper函数。所以这里now’其实表示的是wrapper函数。
可以看到无论是带参数还是不带参的装饰器,其表面上看now()的执行是执行了now,实际上都是执行wrapper函数。不相信的小伙伴可以打印下now的名称(即now.__name__)试一下,其值都是wrapper。表面上看python将now()的执行伪装了,但终究“狐狸的尾巴藏不住啊”,打印下它的函数名称,它就显露原形了。那么为了在平时使用中保持一致性,即加不加装饰器,打印“now.__name__”时都可以得到now函数的原始名字now’,python也给了我们方法,即使用Python内置的functools.wraps即可。不带参和带参的方法各如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
import functools
def log2(text):
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return log
可以看到,其@functools.wraps(func)语句都是在wrapper函数上面加的。
总结
当然python的装饰器使用不止这些。这里只是最基本的理解(若有理解错的,请各位大佬指正哈)。对装饰器的理解首先是要知道函数也是对象,其次只要弄清楚一些函数定义就可以了。当然这里还是得感谢廖雪峰大神的python3教程,正是因为他那清晰透彻的教程,才使我理解装饰器的概念。