实例讲解Python的函数闭包使用中应注意的问题
作者:TypingQuietly 时间:2022-05-17 23:29:53
昨天正当我用十成一阳指功力戳键盘、昏天暗地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()
输出:
4405472064 {'retry_wait': 2, 'retry_times': 3}
4405472064 {'retry_wait': 2, 'retry_times': 3}
23333
4405472064 {'retry_wait': 2, 'retry_times': 2}
23333
4405472064 {'retry_wait': 2, 'retry_times': 1}
23333
4405472064 {'retry_wait': 2, 'retry_times': 0}
从output中可以看到,用dict包装后,程序能够正常的工作,和预期的一致,其实我们也可以从函数的闭包的值再次确认:
>>> test.func_closure[1].cell_contents
{'retry_wait': 2, 'retry_times': 2}
我是结尾,PEACE!
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
ASP 下载时重命名已上传文件的新下载文件名的实现代码
keras多显卡训练方式
jsp页面中获取servlet请求中的参数的办法详解
Python从零开始创建区块链
![](https://img.aspxhome.com/file/2023/3/92203_0s.jpg)
python发送邮件接收邮件示例分享
使用 Python 读取电子表格中的数据实例详解
python 如何将数据写入本地txt文本文件的实现方法
Django修改端口号与地址的三种方式
![](https://img.aspxhome.com/file/2023/3/60983_0s.png)
MySQL的之表结构修改
Python2.x与Python3.x的区别
Python字符串内置函数功能与用法总结
Django渲染Markdown文章目录的方法示例
![](https://img.aspxhome.com/file/2023/9/81569_0s.jpg)
JS实现图片手风琴效果
![](https://img.aspxhome.com/file/2023/6/56066_0s.jpg)
WEB2.0网页制作标准教程(10)自适应高度
Python在字典中获取带权重的随机值实现方式
matplotlib图形整合之多个子图绘制的实例代码
![](https://img.aspxhome.com/file/2023/9/110229_0s.png)
python 自动化办公之批量修改文件名实操
![](https://img.aspxhome.com/file/2023/5/69595_0s.png)
python之数字图像处理方式
![](https://img.aspxhome.com/file/2023/4/78584_0s.jpg)
python中return不返回值的问题解析
![](https://img.aspxhome.com/file/2023/7/63967_0s.jpg)