深入理解python装饰器和闭包

    

  • 闭包
  • 装饰器

参考资料:(大神的作品)

         http://www.cnblogs.com/Jerry-Chou/archive/2012/05/23/python-decorator-explain.html

         https://serholiu.com/python-closures

闭包:

    闭包其实就是在一个函数中嵌套另一个函数的定义。闭包的作用:包括了外部函数的局部变量,这些局部变量在外部函数返回后也继续存在,并能被内部函数引用。

实例如下:

def fun_closule(y):
    """闭包的作用: 当外部函数返回了, 外部函数的局部变量还可以被内部函数引用"""
    print 'id(num): %X', id(y)

    def tmp(x):
        return x * y

    print 'id(tmp): %X', repr(tmp)
    return tmp

if __name__ == '__main__':
    closule = fun_closule(4)

    print 'id(closule): ', repr(closule)

    #当删除了fun_closule对象后, 外部参数还可以被内部函数引用.
    del fun_closule

    print closule(2)

 

结果如下:

id(num): %X 140186159726672
id(tmp): %X 
id(closule):  
8

 

         从结果可以看出,当fun_closule(4) 执行后, 创建和返回了 closule 这个函数对象,内存地址是0x101c5f398, 

并且发现tmp内部函数和它的内存地址相同,即closule只是这个函数对象的一个引用。

 

__closure__ 属性和 cell 对象

         现在知道闭包是怎么一回事了,那就到看看闭包到底是怎么回事的时候了。Python 中函数也是对象,所以函数也有很多属性,和闭包相关的就是 __closure__ 属性。__closure__ 属性定义的是一个包含 cell 对象的元组,其中元组中的每一个 cell 对象用来保存作用域中变量的值。

 

>>> closule.__closure__
(,)
>>> type(closule.__closure__[0])

>>> closule.__closure__[0].cell_contents
4

 

       就如刚才所说,在 closule 的 __closure__ 属性中有外部函数变量y 的引用,通过内存地址可以发现,引用的都是同一个y。如果没用形成闭包,则 __closure__ 属性为 None

装饰器:

      装饰器其实是用闭包来实现的。

装饰器和闭包的关系:

      简单的一个例子:

 

def handle_exception(fn):

    def _decorate():
        print 'before'
        fn()
        print 'after'
    return _decorate


# @handle_exception
def hello():
    print 'hello'

if __name__ == '__main__':
    # print hello()

    res = handle_exception(hello)
    print res()

 

 

使用装饰器,提供一个语法糖@(Syntax Sugar), 如下:
import functools

def handle_exception(fn):

    def _decorate():
        print 'before'
        fn()
        print 'after'
    return _decorate

@handle_exception
def hello():
    print 'hello'

if __name__ == '__main__':
    print hello()

 

 

返回结果:

➜  test python decorate_sample.py
before
hello
after
None

解释: 上述执行的程序返回的都是同一个结果。 在有装饰器下,Python解释器调用hello时,他会将hello转换为handle_exception(hello)()。也就是说真正执行的是_decorate这个函数。

         可以用闭包的概念来解释,res和_decorate函数的内存地址是一样的,装饰器其实是闭包的特例, 其外部函数传的参数是函数名而已。

传参:

 

def handle_exception(num=None):
    def decorator(fn):

        @functools.wraps(fn)
        def __decorator(*args, **kw):

            print "before response"

            fn(*args, **kw)
            print "after respond”, num
        return __decorator
    return decorator

@handle_exception(num)
def func(a):
    print "func "

if __name__ == '__main__':
    func(10)

 

 

结果如下:

 

➜  test python decorate_test.py
before response
func
after respond 10

 

 

解释: 

      其实函数传参, 就是一个原有基础上在外面加一个函数,形成一个闭包,这样函数的参数就是一个闭包的外部参数,不会随着环境改变。

 

多个装饰器:

def logger(fn):
        spec = inspect.getargspec(fn)
        print '\nlogger spec: ', spec

        def _decorator(*args, **kw):

            print "before logger"

            fn(*args, **kw)

            print "after logger"

        return _decorator

def handle_exception(fn):
        spec = inspect.getargspec(fn)
        print '\nhandle_exception spec: ', spec

        def __decorator(self=None, req=None, **kw):

            print "before response"

            fn(self, req, **kw)

            print "after respone", type
        return __decorator
@logger
@handle_exception
def func(self, h):
    print "func "

if __name__ == '__main__':
    func(10, 20)    # 等价于下面的,如果把装饰器去掉  
    # logger(handle_exception(func))()

 

返回结果:

 

➜  test python decorate_test.py
handle_exception spec:  ArgSpec(args=['self', 'h'], varargs=None, keywords=None, defaults=None)

logger spec:  ArgSpec(args=['self', 'req'], varargs=None, keywords='kw', defaults=None)

before logger
before response
func
after respone
after logger

 

 

解释: import inspect主要的作用是检查函数的参数名。可以断点一下,查看程序执行的顺序,有多个装饰器的时候执行顺序是由近到远, 在函数调用的时候, 从远--》近--》函数--》近--》远, 为什么会出现这样的情况呢?

      调用fun(10, 20)函数最后变成了logger函数调用,等价于函数logger(handle_exception(func))(), 参数有handle_exception函数名等,函数名是logger,所以先执行logger里面的_decorator函数,然后依次执行。

为什么import inspect检查的参数名不一样呢?

        

        logger spec: 

       

        从上面的结果可以得到如果把handle_exception装饰器和 hello函数当做一个整体的话,logger装饰器中fn的参数其实是def__decorator(self, req, **kw):里面的参数。 

      可以这样理解整体为handle_exception(hello)(self, req, **kw), 而handle_exception(hello)和__decorator函数的内存地址是一样的。 所以最后得出__decorator(self, req, **kw)。

       

       handle_exception spec:

       

       handle_exception里面的fn其实就是func函数名, 所以参数就是func(self, h)。 

 

python库 decorator:

实例:

 

from decorator import decorator

def logger(fn):

        spec = inspect.getargspec(fn)
        print '\nlogger spec: ', spec

        def _decorator(*args, **kw):

            print "before logger"

            fn(*args, **kw)

            print "after logger"

        return _decorator


@decorator
def handle_exception(fn, self=None, req=None, **kw):
    spec = inspect.getargspec(fn)
    print '\nhandle_exception spec: ', spec

    print "before response"

    fn(self, req, **kw)

    print "after respone", type


@logger
@handle_exception
def func(self, h):
    print "func "

if __name__ == '__main__':
    func(10, 20)
    # logger(handle_exception(func))()

 

结果如下:

➜  test python decorate_test.py

logger spec:  ArgSpec(args=['self', 'h'], varargs=None, keywords=None, defaults=None)
before logger

handle_exception spec:  ArgSpec(args=['self', 'h'], varargs=None, keywords=None, defaults=None)
before response
func
after respone 
after logger

 

将之前实例的handle_exception 换成如上的情况。更加简便,原来还可以这样用。

注意:看inspect查询参数的结果, 这点比较重要, handle_exception和func当做一个整体, 显示的参数名是func的参数名,所以加了异常处理装饰器后不影响logger装饰器的获得的参数名。

场景: 

@logger(category='医药资源-疾病列表-获取疾病详细信息', description='获取疾病详细信息', performance=True)
@http.route('/isleep/disease/get', type='http', auth='user')
@handle_exception
def isleep_disease_get(self, **kw):

 

需要在http.route装饰器中间加一个handle_exception装饰器,同时route装饰器源码里面有个是对接口参数名验证的地方。。怎么办? 可以用上面的方法。 

 

 

你可能感兴趣的:(python)