Python装饰器详解

原创 2016年05月31日 20:12:53

在上一篇文章中我们提到了闭包,也就是将函数作为返回值返回。闭包搞懂了之后,接下来的内容就很简单了。

在定义了许多函数之后,我们希望扩展这些函数的功能,譬如在函数调用前后自动打印日志,但如果是一些通用的功能,修改每一个函数又会显得比较麻烦。最好的方法就是定义一个装饰器,给每个函数增加功能。这种在代码运行期间动态增加函数功能的方式,成为装饰器(Decorator)

一、初始函数

>>> from datetime import datetime
>>> def now():
        print(datetime.now())
>>> now()
2016-05-31 17:04:40.946448

二、增加功能

>>> def now():
        print('run %s().' % now.__name__)
        print(datetime.now())
        print('run %s() finished.' % now.__name__)
>>> now()
run now().
2016-05-31 17:13:15.216500
run now() finished.

这是最普通的增加函数功能的方式了,但如果将它变成通用功能呢?使用装饰器!

三、装饰器

>>> def decorator(fun):
        def wrapper(*args,**kw):
            print('run %s().' % fun.__name__)
            outcome = fun(*args,**kw)
            print('run %s() finished.' % fun.__name__)
            return outcome
        return wrapper
>>> now = decorator(now)
>>> now()
run now().
2016-05-31 17:15:08.001145
run now() finished.

这是装饰器最直观的表示了,定义一个装饰器函数,然后将自己的函数传入,输出的新函数即添加了新功能。

本质上来看,装饰器就是一个高阶函数。由于其接收参数为(*args,**kw),所以能够接受任何形式的调用。在wrapper函数内,调用传入的fun()函数之外,就可以定义新的功能。

但是now = decorator(now)这样的方式似乎很麻烦,所以Python提供了装饰器的语法糖@,使得能够简便地使用装饰器。

四、语法糖

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

>>> def decorator(fun):
        def wrapper(*args,**kw):
            print('run %s().' % fun.__name__)
            outcome = fun(*args,**kw)
            print('run %s() finished.' % fun.__name__)
            return outcome
        return wrapper
>>> @decorator
    def now():
        print(datetime.now())
>>> now()
run now().
2016-05-31 17:10:32.217998
run now() finished.

只需要把@decorator放在函数定义的顶端,再调用函数即为添加功能的新函数。

五、__name__问题

>>> now()
run now().
2016-05-31 17:10:32.217998
run now() finished.
>>> now.__name__
'wrapper'

调用完成之后,新的问题又出现了:函数的__name__属性因为使用装饰器改变了,有些依赖函数签名的代码执行就会出错。

python内置的functools.wraps能够将函数名称替换回来。一个完整的decorator写法如下:

>>> import functools
>>> def decorator(fun):
        @functools.wraps(fun)
        def wrapper(*args,**kw):
            print('run %s().' % fun.__name__)
            outcome = fun(*args,**kw)
            print('run %s() finished.' % fun.__name__)
            return outcome
        return wrapper

你看,实际上这是对wrapper函数的一个装饰器。定义完成之后,就不会除问题了。

>>> @decorator
    def now():
        print(datetime.now())
>>> now()
run now().
2016-05-31 17:32:59.270645
run now() finished.
>>> now.__name__
'now'

六、带参数的Decorator

好了,现在我们已经知道如何实现一个完整的Decorator了,但是,针对不同的函数,我们希望同一个Decorator根据传入的参数,实现不同的功能,这应该怎么实现?

这种情况就需要更复杂的嵌套,首先,需要传入一个参数,然后返回一个装饰器函数,执行上面类似的工作,也就是说,在外面再嵌套一层就可以了。

>>> def decorator(arg):
        def wrapper(func):
            @functools.wraps(func)
            def function(*args,**kw):
                print('%s %s().' % (arg, func.__name__))
                outcome = func(*args,**kw)
                print('%s %s() finished.' % (arg, func.__name__))
                return outcome
            return function
        return wrapper

decorator接收参数,返回wrapper函数,而这个wrapper函数是一个装饰器。内部的运行如下:

>>> decorator('execute')
<function decorator.<locals>.wrapper at 0x0346B6F0>
>>> now = decorator('execute')(now)
>>> now()
execute now().
2016-05-31 19:15:46.935403
execute now() finished.
>>> now.__name__
'now'

decorator('execute')返回的是wrapper函数,然后再传入now函数,最终返回带有新功能&新参数的函数。

最终,使用语法糖的简便写法如下:

>>> @decorator('execute')
    def now():
        print(datetime.now())
>>> now()
execute now().
2016-05-31 19:11:19.024181
execute now() finished.

>>> @decorator('run')
    def now():
        print(datetime.now())
>>> now()
run now().
2016-05-31 19:11:45.361102
run now() finished.

七、终极装饰器

那么,问题来了,我们能不能创建一个装饰器,能够传入参数,也可以不传入参数呢?

这需要对装饰器的运行过程有充分的了解,观察上面的两类装饰器,主要的区别在于:

  • 没有参数的装饰器,最外层接收的是func函数,最终返回的函数是wrapper(*args,**kw),直接接收func函数参数
  • 带参数的装饰器,最外层接收的是参数,而最终返回的函数是wrapper(func),接收func函数

那么,我们就可以通过判定最外层的函数接收的是func函数还是参数,来决定是有参数或者是无参数的装饰器,最终决定如何返回。尝试结果如下:

>>> def decorator(arg):
        if callable(arg):
            @functools.wraps(arg)
            def wrapper(*args,**kw):
                print('run %s().' % arg.__name__)
                outcome = arg(*args,**kw)
                print('run %s() finished.' % arg.__name__)
                return outcome
            return wrapper
        else:
            def wrapper(func):
                @functools.wraps(func)
                def function(*args,**kw):
                    print('%s %s().' % (arg,func.__name__))
                    outcome = func(*args,**kw)
                    print('%s %s() finished.' % (arg,func.__name__))
                    return outcome
                return function
            return wrapper

通过最外层传入的参数是否callable()判断是函数还是参数,进而采取不一样的措施。

>>> @decorator
    def now():
        print(datetime.now())
>>> now()
run now().
2016-05-31 19:51:33.730241
run now() finished.

>>> @decorator('execute')
    def now():
        print(datetime.now())
>>> now()
execute now().
2016-05-31 19:53:09.032116
execute now() finished.

当然,还有另外一种思路,主体按照带参数的装饰器三层嵌套来写,但是最后返回的时候,需要进行判断:如果最初传入的是参数,那么就返回函数;如果最初传入的就是函数,那么就将第二层函数执行后返回。只不过会在内部替换参数的时候有一些局限。

>>> def decorator(arg):
    text = arg if isinstance(arg,str) else ''
    def wrapper(func):
        @functools.wraps(func)
        def function(*args,**kw):
            print(text)
            print('run %s().' % func.__name__)
            outcome = func(*args,**kw)
            print('run %s() finished.' % func.__name__)
            return outcome
        return function
    return wrapper if isinstance(arg,str) else wrapper(arg)

总结

到这里,装饰器的各部分内容都已经很清晰了。了解装饰器首先一定要将闭包是将函数和自由变量共同返回这一概念透彻清晰,其次,装饰器的内部实现原理一定要很了解,这样就没问题了。

(By XuZhiyuan 2016-05-31 20点 @Hohai Rainy)

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

Python装饰器详解

装饰器详解 Python装饰器

python装饰器详解

1、最简单的函数,准备附加额外功能# -*- coding:gbk -*- '''示例1: 最简单的函数,表示调用了两次'''def myfunc(): print("myfunc() cal...

精选:深入理解 Docker 内部原理及网络配置

网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

python装饰器详解

完全参考自 https://segmentfault.com/a/1190000000632572。 讲的很详细 之前用python简单写了一下斐波那契数列的递归实现(如下),发现运行速度很...

详解Python装饰器

一、引出装饰器概念 引入问题:  定义了一个函数,想在运行时动态的增加功能,又不想改动函数本身的代码? 示例: 希望对下列函数调用增加log功能,打印出函数调用: [html] view ...

详解Python的装饰器

详解Python的装饰器 Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里。 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodb...

装饰器详解(python)

本文将介绍装饰器的内容,并举例说明。()

python中装饰器详解

最新学了装饰器,有一个疑问一直困扰我,思考了几天,终于明白。

Python 装饰器详解

转载自:http://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html 理解Python中的装饰器 文章先由stacko...

Python装饰器详解

@author StormMa @date 2017-06-09 生命不息,奋斗不止! 开始之前 第一次接触Python的时候就是直接开始写一些爬虫或者是使用django写简单的web应用...

Python装饰器详解

在上一篇文章中我们提到了闭包,也就是将函数作为返回值返回。闭包搞懂了之后,接下来的内容就很简单了。在定义了许多函数之后,我们希望扩展这些函数的功能,譬如在函数调用前后自动打印日志,但如果是一些通用的功...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)