您的位置:首页 > 编程语言 > Python开发

深入理解python装饰器和闭包

2017-03-30 22:21 429 查看

    

闭包
装饰器
参考资料:(大神的作品)         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 <function tmp at 0x101c5f398>
id(closule):  <function tmp at 0x101c5f398>
8

         从结果可以看出,当fun_closule(4) 执行后, 创建和返回了 
closule
 这个函数对象,内存地址是0x101c5f398, 并且发现tmp内部函数和它的内存地址相同,即closule只是这个函数对象的一个引用。

__closure__ 属性和 cell 对象

         现在知道闭包是怎么一回事了,那就到看看闭包到底是怎么回事的时候了。Python 中函数也是对象,所以函数也有很多属性,和闭包相关的就是 
__closure__
 属性。
__closure__
 属性定义的是一个包含 cell 对象的元组,其中元组中的每一个 cell 对象用来保存作用域中变量的值。>>> closule.__closure__
(<cell at 0x2bf4ec0: int object at 0x101c5f398>,)
>>> type(closule.__closure__[0])
<type 'cell'>
>>> 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()
def func(num):
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 <type 'type'>
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 闭包 装饰器