视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
实例讲解Python的函数闭包使用中应注意的问题
2020-11-27 14:29:07 责编:小采
文档

昨天正当我用十成一阳指功力戳键盘、昏天暗地coding的时候,正好被人问了一个问题,差点没收好功,洪荒之力侧漏震伤桌边的人,废话不多说,先上栗子(精简版,只为说明问题):

from functools import wraps
from time import sleep

def retry(attempts=3, wait=2):
 if attempts < 0 or attempts > 5:
 retry_times = 3
 else:
 retry_times = attempts
 if wait < 0 or wait > 5:
 retry_wait = 2
 else:
 retry_wait = after
 def retry_decorator(func):
 @wraps(func)
 def wrapped_function(*args, **kwargs):
 while retry_times > 0:
 try:
 return func(*args, **kwargs)
 except :
 sleep(retry_wait)
 retry_times -= 1
 return wrapped_function
 return retry_decorator

简易版的retry装饰器,需要的变量被闭包完美捕捉,逻辑也挺简单明了。问的人说逻辑看着挺正常的,但就是一直报变量retry_times找不到(unresolved reference)的错误提示。

没错仔细捋一下,这是一道送分题呢:闭包捕获的变量(retry_times,retry_wait)相当时引用的retry函数的局部变量,当在wrapped_function的局部作用于里面操作不可变类型的数据时,会生成新的局部变量,但是新生成的局部变量retry_times在使用时还没来得及初始化,因此会提示找不到变量;retry_wait相反能被好好的使用到。

python是duck-typing的编程语言,就算有warning照样跑,写个简单到极限的的函数,用一下装饰器,在wrapped_function逻辑里打个断点看一下各个变量的值也是很快能找到问题的(直接跑也能看到错误:UnboundLocalError: local variable 'retry_attempts' referenced before assignment, 至少比warning msg有用):

@retry(7, 8)
def test():
 print 23333
 raise Exception('Call me exception 2333.')

if __name__ == '__main__':
 test()

output: UnboundLocalError: local variable 'retry_times' referenced before assignment

要解决这种问题也好办,用一个可变的容器把要用的不可变类型的数据包装一下就行了(说个好久没写C#代码记不太清楚完全不负责任的题外话,就像在C#.net里面,碰到闭包的时候,会自动生成一个混淆过名字的类然后把要被捕捉的值当作类的属性存着,这样在使用的时候就能轻松get,著名的老赵好像有一篇文章讲Lazy Evaluation的好像涉及到这个话题):

def retry(attempts=3, wait=2):
 temp_dict = {
 'retry_times': 3 if attempts < 0 or attempts > 5 else attempts,
 'retry_wait': 2 if wait < 0 or wait > 5 else wait
 }

 def retry_decorate(fn):
 @wraps(fn)
 def wrapped_function(*args, **kwargs):
 print id(temp_dict), temp_dict
 while temp_dict.get('retry_times') > 0:
 try:
 return fn(*args, **kwargs)
 except :
 sleep(temp_dict.get('retry_wait'))
 temp_dict['retry_times'] = temp_dict.get('retry_times') - 1
 print id(temp_dict), temp_dict

 print id(temp_dict), temp_dict

 return wrapped_function

 return retry_decorate

@retry(7, 8)
def test():
 print 23333
 raise Exception('Call me exception 2333.')

if __name__ == '__main__':
 test()

输出:

44054720 {'retry_wait': 2, 'retry_times': 3}
44054720 {'retry_wait': 2, 'retry_times': 3}
23333
44054720 {'retry_wait': 2, 'retry_times': 2}
23333
44054720 {'retry_wait': 2, 'retry_times': 1}
23333
44054720 {'retry_wait': 2, 'retry_times': 0}

从output中可以看到,用dict包装后,程序能够正常的工作,和预期的一致,其实我们也可以从函数的闭包的值再次确认:

>>> test.func_closure[1].cell_contents
{'retry_wait': 2, 'retry_times': 2}

我是结尾,PEACE!

下载本文
显示全文
专题