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 装饰器:16 步轻松搞定 Python 装饰器

目录[-] 1. 函数 2. 作用域 3. 变量解析规则 4. 变量生存周期 5. 函数参数 6. 嵌套函数 7. 函数是python世界里的一级类对象 8. 闭包 9. 装...

python(四)下:python装饰器详解

装饰器一、介绍 器:代表函数的意思。装饰器本质就是是函数 功能:装饰其他函数,就是为其他函数添加附加功能 被装饰函数感受不到装饰器的存在 原则: 不能修改被装饰的函数的源代码(比如线上环境) ...
  • fgf00
  • fgf00
  • 2016年08月25日 23:52
  • 1452

[Python入门及进阶笔记]Python-decorator装饰器小结

  • wklken
  • wklken
  • 2012年10月27日 17:34
  • 29297

python装饰器

wrapper,最近被问到这个问题,简单地会python其实挺尴尬的,因为之前有某一次,一个面试官跟我说,“python,我也可以会啊,只不过是要查文档而已”。确实,如果只会基本的数据结构(列表、字符...

Python装饰器

python装饰器首先要明白的一点是,装饰器装饰的是函数。python之中为什么会有这样的需求?需求是怎么来的def foo(): print 'in foo()'foo()这是一个函数,如果...

Python装饰器学习

在《Core Python Programming 2nd》中学习到了装饰器,这对我来说是个完全陌生的语法,第一遍愣是没看懂,很有必要记一下。 第一眼看到这个词Decorator,我联想到了DP中的D...
  • thy38
  • thy38
  • 2009年08月21日 22:45
  • 49347

Python实战小程序——装饰器

第四题:简述对Python装饰器的理解,写一个简单的装饰器。 要理解装饰器,我们先介绍一下几点python的基础知识。 1、作用域(命名空间)及变量生存周期 有过一点编程基础的都知道namesp...

浅谈Python装饰器

浅谈Python装饰器 By 马冬亮(凝霜  Loki) 一个人的战争(http://blog.csdn.net/MDL13412) 前置知识 一级对象 Python将一切视为 objec t的...
  • MDL13412
  • MDL13412
  • 2014年03月30日 22:07
  • 36816

Python中装饰器的应用

  • 2014年11月08日 00:18
  • 77KB
  • 下载

Python装饰器decoder.py

  • 2017年11月15日 14:28
  • 7KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Python装饰器详解
举报原因:
原因补充:

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