在理解学习装饰器之前,首先要将 Python 中函数视作对象.如下我们在定义一个函数后,确定函数对象本身是 function 类的实例,并通过别的名称使用函数,将函数作为参数传递.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def  func (n) :    return  1  if  n < 2  else  n * func(n-1 ) >>>  func(5 )120 >>>  type(func) <class  'function '> >>> fact = func # 通过别的名称使用函数 >>>  fact<function func at 0x000001C68E521E18 > >>>  fact(5 )120 >>>  map(func, range(11 )) <map object at 0 x...> >>>  list(map(fact, range(11 )))[1 , 1 , 2 , 6 , 24 , 120 , 720 , 5040 , 40320 , 362880 , 3628800 ] 
 
高阶函数 
接受函数作为参数,或将函数作为结果返回的函数是高阶函数.
 
如上述示例中的 map 函数,它以我们定义的 func 函数作为参数传递,实现高阶函数的效果.另外,内置函数 sorted 函数也是.可选的 key 参数用于提供一个函数,此函数会应用到各个元素上,并以函数的返回结果进行排序.
1 2 3 >>>  fruits = ['strawberry' , 'fig' , 'apple' , 'cherry' , 'raspberry' , 'banana' ]>>>  sorted(fruits, key=len)['fig' , 'apple' , 'cherry' , 'banana' , 'raspberry' , 'strawberry' ] 
 
最为人熟知的高阶函数有 map,filter, sorted,reduce.其中 map,filter 可以被列表推导式或生成器推导式所替代.reduce 函数从 Python 3.0 开始就不会内置函数了,需要从 functools 包导入(from functools import reduce).
装饰器原理 装饰器函数其实是这样一个接口约束,它必须接受一个可调用对象作为参数,然后返回一个可调用对象.使用装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能 .
可调用对象详见 Python 对象分类  中可调用对象小节.
 
假设有如下装饰器:
1 2 3 4 5 6 7 8 9 def  deco (func) :    def  inner () :         print('running inner()' )     return  inner @decorate def  target () :    print('running target()' ) 
 
上述代码的效果与下述写法一样:
1 2 3 4 5 6 7 8 9 10 def  deco (func) :    def  inner () :         print('running inner()' )     return  inner def  target () :    print('running target()' ) target = decorate(target) 
 
严格的来说,装饰器只是语法糖.装饰器可以像常规可调用对象那样调用,其参数是另一个函数.
装饰器能将被装饰的函数替换成其它函数 
装饰器在加载模块时立即执行 
 
Python 何时执行装饰器 装饰器在被装饰的函数定义之后立即运行.这通常发生在导入模块时.如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 registry = [] def  register (func) :         print('running register(%s)'  % func)     registry.append(func)     return  func @register def  f1 () :    print('running f1()' ) @register def  f2 () :    print('running f2()' ) def  f3 () :    print('running f3()' ) def  main () :    print('running main()' )     print('registry ->' , registry)     f1()     f2()     f3() if  __name__=='__main__' :    main() 
 
直接执行,结果如下:  
1 2 3 4 5 6 7 8 $ python3 registration.py running register(<function f1 at 0x100631bf8>) running register(<function f2 at 0x100631c80>) running main() registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1() running f2() running f3() 
 
若作为模块导入,输出如下
1 2 3 4 5 >>>  import  registrationrunning register(<function f1 at 0x10063b1e0 >) running register(<function f2 at 0x10063b268 >) >>>  registration.registry  [<function f1 at 0x10063b1e0 >, <function f2 at 0x10063b268 >] 
 
上述情况应该不难理解.装饰器若在导入的模块中应用,则相当于在导入的模块中调用了两次装饰器的定义函数(上述示例中为 register),并将函数的返回值赋值给了 f1 与 f2.具体可参见注释部分.
考虑到真实代码中的常用方式,上述代码有两个不同的地方:
上述代码中装饰器函数与被装饰函数在同一模块中定义,而真实情况中,装饰器通常在一个模块中定义,然后应用到其它模块中的函数上.此时就不是导入时运行了,而可以理解为应用时运行(python 解释器解释到被装饰函数或类时,装饰器运行) 
register 装饰器返回的函数与通过参数传入的相同.但是,实际上,大多数装饰器会在内部定义一个函数,然后将其返回. 
 
装饰器实现 计时器实现 如下示例实现了一个装饰器,用于计算被装饰函数的运行时间
1 2 3 4 5 6 7 8 9 10 11 12 import  timedef  clock (func) :    def  clocked () :         start = time.time()         result = func()         name = func.__name__         end = time.time()         print('%s function spent %s'  % (name, (end-start)))         return  result     return  clocked 
 
使用如上定义的装饰器:
1 2 3 4 5 6 7 8 9 10 11 from  clockdeco_demo import  clock@clock def  target () :    time.sleep(2 ) if  __name__ == '__main__' :    target() 
 
我们来分析一下如上装饰器的使用过程使用.可以转换为如下代码
1 2 3 4 5 6 7 8 9 from  clockdeco_demo import  clockdef  target () :    time.sleep(2 ) target = clock(target) if  __name__ == '__main__' :    target() 
 
后续分析如下
1 2 3 4 5 6 7 8 9 target = clock(target) -> clocked target() = clocked() 
 
修改过的装饰器函数如下:
1 2 3 4 5 6 7 8 9 10 11 import  timedef  clock (func) :    def  clocked (*args, **kwargs) :           start = time.time()         result = func(*args, **kwargs)         name = func.__name__         end = time.time()         print('%s function spent %s'  % (name, (end-start)))         return  result     return  clocked 
 
带参数的装饰器 假设有这样一种场景,我想通过装饰器打印日志输出,并可以通过装饰器中参数来调整日志的输出级别.如 @logging(level='INFO'),@logging(level='DEBUG'),这要如何实现呢?
OK,我们先来看如下带参数的装饰器(与前一小节中差不多):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 def  logging (func) :    def  wrapper (*args, **kwargs) :         result = func(*args, **kwargs)         name = func.__name__         print('%s function loggging %s %s'  % (name,*args, *kwargs))         return  result     return  wrapper @logging def  app (name, age) :    user={         'name' :name,         'age' : age,     }     return  user                                  if  __name__ == '__main__' :    print(app('tom' ,20 )) 
 
那么,我们如何在装饰器上使用添加函数呢?
在如上示例中,@logging 等价于 app = logging(app) -> wrapper.如果装饰器带上函数,则可以理解为 @logging(level='INFO') 等价于 app = logging(level='INFO')(app)-> wrapper(app),就相当于继续调用内层函数 wrapper,并将 app 作为参数传入.很显然,上述代码中,内层函数 wrapper 返回结果为 app 函数的返回结果 user,是不可调用对象.因此,我们需要使内层函数 wrapper 继续返回一个可调用对象 inner_wrapper,而所有操作(打印日志,执行原始 app 函数)均在 inner_wrapper 中执行.代码如下:
1 2 3 4 5 6 7 8 9 def  logging (level) :    def  wrapper (func) :         def  inner_wrapper (*args, **kwargs) :             result = func(*args, **kwargs)             name = func.__name__             print('%s: %s function loggging %s %s'  % (level, name,*args, *kwargs))             return  result         return  inner_wrapper     return  wrapper 
 
尝试使用一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @logging('INFO') def  info_app (name, age) :    user={         'name' :name,         'age' : age,     }     return  user @logging('ERROR') def  err_app (name, age) :    user={         'name' :name,         'age' : age,     }     return  user if  __name__ == '__main__' :    print(app('tom' ,20 ))     print(err_app('jerry' ,19 )) INFO: info_app function loggging tom 20  {'name' : 'tom' , 'age' : 20 } ERROR: err_app function loggging jerry 19  {'name' : 'jerry' , 'age' : 19 } 
 
此时再来分析一下 @logging(level='INFO'),它可以做如下转换:
1 2 3 4 app = logging(level='INFO')(app) -> wrapper(app) -> inner_wrapper app() = inner_wrapper() -> result 
 
上述代码的转换,其实是在不带参数的装饰器的基础上又在最外层嵌套了一层函数.
装饰器类 装饰器类的实现其实与装饰器函数的实现大同小异.只要符合接受一个可调用对象作为参数,并返回一个可调用对象 的可调用的类(实现了 __call__() 方法)就是一个装饰器类.
与装饰器函数类似,装饰器类的实现,需要我们:
将被装饰的对象必须作为类的初始化参数传入装饰器类. 
在装饰器类的 __call__ 方法中定义装饰器进行的操作,修饰并调用被装饰的可调用对象,并返回被装饰对象的调用结果 
 
见如下实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class  logging (object) :    def  __init__ (self, func) :         '''          装饰器类的初始化函数,必须传入一个参数         :param func: 被装饰的对象         '''         self.func = func     def  __call__ (self, *args, **kwargs) :         '''          在此方法中定义装饰器的内容,并返回被装饰对象的调用结果         :param args, kwargs: 为被装饰对象的参数         '''         print("[DEBUG]: enter function {func}()" .format(func=self.func.__name__))         return  self.func(*args, **kwargs) @logging def  say (something) :    print("say {}!" .format(something)) if  __name__ == '__main__' :    say('decorator class' ) 
 
在上述示例中,只要知道 @logging 等价于 say = logging(say) 就不难理解,这一步骤实际上做的事情是以 say 函数作为初始化参数创建了 logging 类的实例对象.在调用 say('decorator class') 时,实际上是对 logging 类的实例进行了调用.从而调用了实例的 __call__ 方法.
带参数的装饰器类 参参数的装饰器类可以看作是装饰器类与带参数的装饰器函数的结合体.
与装饰器函数类似,我们需要
将装饰器类的参数传入装饰器类的初始化函数中 
在装饰器类的 __call__ 方法中定义装饰器进行的操作,修饰并调用被装饰的可调用对象,并返回一个可调用对象 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class  logging (object) :    def  __init__ (self, level='INFO' ) :         self.level = level     def  __call__ (self, func) :         '''          在此方法中定义装饰器的内容,并返回可调用对象         :param func: 被装饰对象         :return: 返回可调用对象         '''         def  wrapper (*args, **kwargs) :             print("[{level}]: enter function {func}()" .format(                 level=self.level,                 func=func.__name__))             return  func(*args, **kwargs)         return  wrapper @logging(level='INFO') def  say (something) :    print("return {}!" .format(something))     return  something if  __name__ == '__main__' :    print(say('tom' )) 
 
可通过方法自定义属性的装饰器 参考 Python Cookbook 中文第 3 版 - "9.5 可自定义属性的装饰器"章节.
Q: 你想写一个装饰器来包装一个函数,并且在运行时允许用户提供参数控制装饰器行为 
A: 引入一个访问函数,使用 nonlocal 来修改内部变量.然后这个访问函数被作为一个属性赋值给包装函数 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from  functools import  wraps, partialimport  loggingdef  attach_wrapper (obj, func=None) :    if  func is  None :         return  partial(attach_wrapper, obj)              setattr(obj, func.__name__, func)      return  func def  logged (level, name=None, message=None) :    def  decorate (func) :         logname = name if  name else  func.__module__         log = logging.getLogger(logname)         logmsg = message if  message else  func.__name__         @wraps(func)         def  wrapper (*args, **kwargs) :             log.log(level, logmsg)             return  func(*args, **kwargs)         @attach_wrapper(wrapper)         def  set_message (newmsg) :             nonlocal  logmsg             logmsg = newmsg                                                                                 return  wrapper     return  decorate @logged(logging.WARNING) def  add (x, y) :    return  x + y if  __name__ == '__main__' :    add.set_message('Add called' )     add(10 , 20 )      
 
装饰器注意事项 装饰器内代码的执行顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def  logging (level) :    print('outer function begin' )     def  wrapper (func) :         print('wrapper function begin' )         def  inner_wrapper (*args, **kwargs) :             print('inner function begin' )             print("[{level}]: enter function {func}()" .format(                 level=level,                 func=func.__name__))             print('inner function end' )             return  func(*args, **kwargs)         print('wrapper function end' )         return  inner_wrapper     print('outer function end' )     return  wrapper @logging(level='debug') def  hello () :   print('hello' ) 
 
错误的属性 任何被装饰器装饰过的对象,其实已经变成装饰器函数返回的可调用对象.导致被装饰对象的所有内置属性,都会变成装饰器函数返回的可调用对象的内置属性.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from  datetime import  datetimedef  logging (func) :    def  wrapper (*args, **kwargs) :         """print log before a function."""          print("[DEBUG] {}: enter {}()" .format(datetime.now(), func.__name__))         return  func(*args, **kwargs)     return  wrapper @logging # 等同于 say=logging(say),导致 say 变量的所有内置属性,都会变成 wrapper 函数的内置属性 def  say (something) :    """say something"""      print("say {}!" .format(something)) print(say.__name__)   
 
解决: 使用标准库里的 functools.wraps 装饰器对待返回的可调用对象进行装饰,可以基本解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from  functools import  wrapsdef  logging (func) :          @wraps(func)     def  wrapper (*args, **kwargs) :         """print log before a function."""          print  "[DEBUG] {}: enter {}()" .format(datetime.now(), func.__name__)         return  func(*args, **kwargs)     return  wrapper @logging def  say (something) :    """say something"""      print("say {}!" .format(something)) print(say.__name__)   print(say.__doc__)  
 
装饰器第三方库 1 2 3 4 5 6 7 8 9 10 from  decorator import  decorator@decorator def  logging (func, *args, **kwargs) :    print  "[DEBUG] {}: enter {}()" .format(datetime.now(), func.__name__)     return  func(*args, **kwargs) @logging def  say (something) :    pass  
 
1 2 3 4 5 6 7 8 9 10 11 import  wrapt@wrapt.decorator def  logging (wrapped, instance, args, kwargs) :       print  "[DEBUG]: enter {}()" .format(wrapped.__name__)     return  wrapped(*args, **kwargs) @logging def  say (something) :    pass