装饰器Decorator(函数的装饰)

一。LEGB函数作用域的优先级和解析

    函数是function的一个对象,被调用完后内部变量就会被回收,被引用的除外(例如return的变量)

    1.   local :函数内部作用域

    2.  enclosing :函数内部和内嵌函数之间(一般是闭包使用)

    3.  global :全局作用域

    4.  build-in :内置作用域(Python自带的)

    当多个作用域有相同的变量时,优先级高的会覆盖优先级低的,优先级从高到低1到4.

二。闭包

   1.  什么是闭包

>>> def my_func(val):
...     def func():
...         print val
...     return func
... 
>>> f = my_func('a')
>>> f()
a
>>> 

    在my_func这个函数里,内嵌的func函数就是闭包,而闭包调用了enclosing作用域的val,my_func函数调用完后,val理应被回收了,但却还能被闭包调用,这是因为闭包的__closure__属性。

   2. 闭包的__closure__属性

>>> def my_func(val):
...     print '%x' % id(val)
...     def func():
...         print val
...     return func
... 
>>> f = my_func('a')
10ce56580
>>> print f.__closure__
(<cell at 0x10cf52980: str object at 0x10ce56580>,)
>>> 

    可以看到闭包的__closure__属性保存了一个内存地址,而这个内存地址就是my_func函数里变量val的内存地址。这就是val没被回收的原因,这是闭包的一个强大的作用之一。

三。装饰器(Decorator): 在代码运行期间动态增加功能的方式。

   1.  用闭包装饰函数my_func_01,为他添加打印的b的功能

>>> def my_func(fun):
...     def func():
...         print 'b'
...         return fun()
...     return func
... 
>>> def my_func_01():
...     print 'a'
... 
>>> my_func_01 = my_func(my_func_01)
>>> my_func_01()
b
a
>>> 

   2.  用语法糖@简化闭包的装饰

>>> def my_func(fun):
...     def func():
...         print 'b'
...         return fun()
...     return func
... 
>>> @my_func
... def my_func_01():
...     print 'a'
... 
>>> my_func_01()
b
a
>>> 

  所以可知道@my_func就等于 my_func_01 = my_func(my_func_01)

四。简单函数装饰

    为my_fun(a) 函数添加一个打印功能   print 'add_fun'

   @add_log  就相当于   my_fun = add_log(my_fun)

>>> def add_log(func):
...     def wrapper(*args,**kw):
...             print 'add_fun'
...             return func(*args,**kw)
...     return wrapper
... 
>>> @add_log
... def my_fun(a):
...     print 'my_fun'
...     return 'a'
... 
>>> my_fun(4)
add_fun
my_fun
'a'
>>> 

     my_fun 指向了新的函数 wrapper(*args,**kw) , return func(*args,**kw) 而不是 return func ,这样一来,my_fun 所传入的参数就要和 my_fun(a) 的参数统一起来(当然也可以这样 my_fun(*args,**kw)) , 然后返回 func(*args,**kw) 的执行结果给 my_fun 变量,就实现了装饰作用。

五。带参数的装饰器

    可以传入参数的装饰器,也可以这样 def add_log(*args,**kw): 来传多个参数。

    @add_log('qwe')    就相当于 my_fun = add_log('wer')(my_fun) , 也就相当于 my_fun1 = add_log('wer') 和 @my_fun1 结合. 

>>> def add_log(text):
...     def log_decorator(func):
...             def wrapper(*args,**kw):
...                 print 'add_fun'
...                 print text
...                 return func(*args,**kw)
...         return wrapper
...     return log_decorator
... 
>>> @add_log('qwe')
... def my_fun(a):
...     print 'my_fun'
...     return 'a'
... 
>>> my_fun(4)
add_fun
qwe
my_fun
'a'
>>> 

六。完善装饰器

     my_fun 指向了新的函数 ,那一些属性就不是原来 my_fun 函数的了,这对有些依赖函数签名的代码执行就会出错(其实并不懂啥是依赖函数签名的代码0.0)。

>>> my_fun.__name__
'wrapper'
>>> 

   就要在装饰器里添加 wrapper.__name__ = my_fun.__name__ 和 wrapper.__doc__ = my_fun.__doc__ 等代码,可以直接用Python内置的functools.wraps来处理。

>>> import functools
>>> def add_log(func):
...     @functools.wraps(func)
...     def wrapper(*args,**kw):
...             print 'add_fun'
...             return func(*args,**kw)
...     return wrapper
... 
>>> @add_log
... def my_fun(a):
...     print 'my_fun'
...     return 'a'
... 
>>> print my_fun.__name__
my_fun
>>> 

   最后需要指出,由于我们把原函数签名改成了(*args, **kw),因此,无法获得原函数的原始参数信息。即便我们采用固定参数来装饰只有一个参数的函数

四。参考

https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819879946007bbf6ad052463ab18034f0254bf355000

http://www.imooc.com/code/6067

https://www.imooc.com/learn/581

原文地址:https://www.cnblogs.com/GH-123/p/7787163.html