深入浅出python装饰器

python_decorator_logo
python_decorator_logo

python的装饰器是一种非常实用的功能。它可以在不改变现有函数的前提下增加该函数的功能。这样可以可以将大量重复的代码抽离出来提高代码复用率,在重构中让代码更加简洁清晰。尤其是在AOP的时候,这种优势就极为明显。说到这里熟悉Java的朋友多半会联想到诸如:javassist,CGLIB,AspectJ这些工具吧,没错与在Java中这些工具的功能类似,装饰器也具有同样的特性。在学习装饰器之前,各位看官姥爷应该对python闭包有一定的了解。如果不熟悉闭包,请参考我的前一篇文章《python闭包核心》,下面让我们先来看两个简单的例子,两个简单的函数:

# coding=utf-8
def good():
        print 'good news'

def bad():
        print 'bad news'

flag = 1
if flag:
        good()
else:
        bad()

输入结果为:

good news

上面是一个简单的逻辑判断,当flag为1的时候调用good()函数,那么接下来我们稍微改造一下,在不修改good,bad函数本身的前提下,在执行两个函数前输出一段信息。

# coding=utf-8
def good():
        print 'good news'

def bad():
        print 'bad news'

def show(f):
        print 'welcome here~'
        f()

flag = 1
if flag:
        show(good)
else:
        show(bad)

输出结果为:

welcome here~
good news

此时调用方法已经改变,那么我们能否在不修改good,bad函数同时不修改good(),bad()调用方法的前提下,在执行两个函数前输出一段信息?

# coding=utf-8
def good():
        print 'good news'

def bad():
        print 'bad news'


def showmore(f): #这里是闭包同时也是装饰器
        def decorator():
                print 'welcome here~'
                f()
        return decorator


good = showmore(good)
bad = showmore(bad)

flag = 1
if flag:
        good()
else:
        bad()

输出结果为:

welcome here~
good news

上述代码输出结果与之前的一致,并且我们做到了没有修改good,bad函数的同时也没有修改good(),bad()调用方法。这就是装饰器的强大之处。在python中装饰器有特定的语法来实现(修改下装饰器语法):

# coding=utf-8
def showmore(f):
        def decorator():
                print 'welcome here~'
                f()
        return decorator

@showmore
def good():
        print 'good news'

@showmore
def bad():
        print 'bad news'

flag = 1
if flag:
        good()
else:
        bad()

上述代码与我们自己实现的方式是一致的,看起来是不是更简洁?作为一个曾经的Java爱好者忍不住想到了Tapestry4.1x和Tapestry5中的Annotations,没错,简直一模一样。接下来,如果我们的函数带有参数该如何去调用呢?

# coding=utf-8
def showmore(f):
        def decorator(name): #参数在这里
                print 'welcome here~'+name
                #f(name) #注意,这里被注释掉了
        return decorator

@showmore
def good(n):
        print 'good news '+n

@showmore
def bad(n):
        print 'bad news '+ n

flag = 1
if flag:
        good('Mr.Good')
else:
        bad('Mr.Bad')

运行结果为:

welcome here~Mr.Good

参数的传递是不是很简单?但输出结果又有一点点意外?为何good函数中的输出没有被打印出来?看到上面注掉的的那一句了吗?那就是没有输出的原因。这里good经过装饰实际已经被替换成了decorator函数,带有输出语句的原始good函数则是作为参数f被传递到了showmore函数中。因此在decorator中通过调用f(name)实际就是调用了带有输出的原始good函数。(所以说装饰器实际就是闭包的一种高阶应用),为了证明这一点,我们改进一下:

# coding=utf-8
def showmore(f):
        print f('我是没有经过装饰的原始函数') #这里调用传递过来的函数
        def decorator(name):
                print 'welcome here~'+name
                f(name)
        return decorator

@showmore
def good(n):
        print 'good news '+n

@showmore
def bad(n):
        print 'bad news '+ n

flag = 1
if flag:
        good('Mr.Good')
else:
        bad('Mr.Bad')

起输出结果为:

good news 我是没有经过装饰的原始函数
None
bad news 我是没有经过装饰的原始函数
None
welcome here~Mr.Good
good news Mr.Good

通过debug可以看到f确实就是原始的good和bad函数。在执行到good函数的@showmore时候直接跳转到showmore(f)函数,此过程相当于good = showmore(good),这也与前文例子一样。因为闭包的特性,此时内部函数并没有执行,但print f(‘我是没有经过装饰的原始函数’)可以执行。那么为什么会是下面这种诡异的结果呢?

good news 我是没有经过装饰的原始函数
None

原因在于f(‘我是没有经过装饰的原始函数’)就是good(n)函数,在执行good(n)函数时可以打印出“good news 我是没有经过装饰的原始函数”,但因为good(n)函数没有返回值,所以此时print出来的就是None。那么问题来了,如果有返回值呢(让good函数有返回值)?

# coding=utf-8
def showmore(f):
        print f('我是没有经过装饰的原始函数')
        def decorator(name):
                print 'welcome here~'+name
                f(name)
        return decorator

@showmore
def good(n):
        print 'good news '+n
        return 'goodbye~'+n #这里增加了返回值

@showmore
def bad(n):
        print 'bad news '+ n

flag = 1
if flag:
        good('Mr.Good')
else:
        bad('Mr.Bad')

输出结果为:

good news 我是没有经过装饰的原始函数
goodbye~我是没有经过装饰的原始函数
bad news 我是没有经过装饰的原始函数
None
welcome here~Mr.Good
good news Mr.Good

与我们上面分析的一样,当good(n)函数有返回值,那么print的结果就不是None。同理,上面说过good(‘Mr.Good’)和bad(‘Mr.Bad’)实际上都是被装饰器装饰过的函数(就是替换成了decorator(name)函数),为了证明这点,我们来测试下,输出good(‘Mr.Good’),看看到底是什么:

# coding=utf-8
def showmore(f):
        def decorator(name):
                print 'welcome here~'+name
                f(name)
        return decorator

@showmore
def good(n):
        print 'good news '+n

@showmore
def bad(n):
        print 'bad news '+ n

flag = 1
if flag:
        print good('Mr.Good')
else:
        print bad('Mr.Bad')

输出结果为:

welcome here~Mr.Good
good news Mr.Good
None

估计看到这个结果您已经不难才出原因了,这正是decorator(name)函数的输出结果,两个打印分别是自己的print和调用没被装饰过的good(n)函数打印出来的,至于None,因为decorator(name)函数本身没有返值所以就是None。那么如果我们让decorator(name)有返回值呢?让decorator(name)函数带有返回值,于是我们得到了一个带有返回值的装饰器:

# coding=utf-8
def showmore(f):
        def decorator(name):
                print 'welcome here~'+name
                f(name)
                return 'goodbye~~'+name #这里增加了返回值
        return decorator

@showmore
def good(n):
        print 'good news '+n

@showmore
def bad(n):
        print 'bad news '+ n

flag = 1
if flag:
        print good('Mr.Good')
else:
        print bad('Mr.Bad')

输出结果为:

welcome here~Mr.Good
good news Mr.Good
goodbye~~Mr.Good

果然如我们所料想的,有了返回值。最后我们再看一个有趣的例子,如果多个装饰器作用在同一个函数上会是啥样子?

# coding=utf-8
def showmore(f): #装饰器1
        def decorator(name):
                print 'welcome here~'+name
                f(name)
                return 'goodbye~~'+name
        return decorator

def printPre(f): #装饰器2
        def showPre(name):
                print "$"*20
                f(name)
                return '$~~' + name
        return showPre

def printSufx(f): #装饰器3
        def showSufx(name):
                print "*"*20
                f(name)
                return '*~~' + name
        return showSufx


@printPre
@showmore
@printSufx
def good(n):
        print 'good news '+n

@showmore
def bad(n):
        print 'bad news '+ n

flag = 1
if flag:
        print good('Mr.Good')
else:
        print bad('Mr.Bad')

输出结果为:

$$$$$$$$$$$$$$$$$$$$
welcome here~Mr.Good
********************
good news Mr.Good
$~~Mr.Good
About 歇歇脚|Java|Linux 1036 Articles
歇歇脚元老