实例讲解Python的函数闭包使用中应注意的问题


Posted in Python onJune 20, 2016

昨天正当我用十成一阳指功力戳键盘、昏天暗地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!

Python 相关文章推荐
python简单读取大文件的方法
Jul 01 Python
利用python求相邻数的方法示例
Aug 18 Python
Django跨域请求问题的解决方法示例
Jun 16 Python
Python实现的括号匹配判断功能示例
Aug 25 Python
自学python的建议和周期预算
Jan 30 Python
keras获得某一层或者某层权重的输出实例
Jan 24 Python
浅析matlab中imadjust函数
Feb 27 Python
Django-xadmin后台导入json数据及后台显示信息图标和主题更改方式
Mar 11 Python
Pytorch通过保存为ONNX模型转TensorRT5的实现
May 25 Python
python爬虫利器之requests库的用法(超全面的爬取网页案例)
Dec 17 Python
Python源码解析之List
May 21 Python
Python NumPy灰度图像的压缩原理讲解
Aug 04 Python
Python中的数学运算操作符使用进阶
Jun 20 #Python
Python中在for循环中嵌套使用if和else语句的技巧
Jun 20 #Python
解析Python中的生成器及其与迭代器的差异
Jun 20 #Python
Python判断列表是否已排序的各种方法及其性能分析
Jun 20 #Python
Python编程中装饰器的使用示例解析
Jun 20 #Python
12步入门Python中的decorator装饰器使用方法
Jun 20 #Python
深入学习Python中的装饰器使用
Jun 20 #Python
You might like
php microtime获取浮点的时间戳
2010/02/21 PHP
PHP图片添加水印功能示例小结
2016/10/03 PHP
JavaScript 编程引入命名空间的方法
2007/06/29 Javascript
Javascript YUI 读码日记之 YAHOO.util.Dom - Part.3
2008/03/22 Javascript
用于判断用户注册时,密码强度的JS代码
2009/01/01 Javascript
js 代码集(学习js的朋友可以看下)
2009/07/22 Javascript
面向对象的javascript(笔记)
2009/10/06 Javascript
javascript当onmousedown、onmouseup、onclick同时应用于同一个标签节点Element
2010/01/05 Javascript
javaScript 页面自动加载事件详解
2014/02/10 Javascript
js跨域访问示例(客户端/服务端)
2014/05/19 Javascript
12306验证码破解思路分享
2015/03/25 Javascript
jQuery选择器源码解读(一):Sizzle方法
2015/03/31 Javascript
javascript获取重复次数最多的字符
2015/07/08 Javascript
jquery实现鼠标滑过显示二级下拉菜单效果
2015/08/24 Javascript
非常实用的12个jquery代码片段
2015/11/02 Javascript
ES6概念 ymbol.for()方法
2016/12/25 Javascript
javascript实现的图片预览功能
2017/03/25 Javascript
详解JS模块导入导出
2017/12/20 Javascript
Phaser.js实现简单的跑酷游戏附源码下载
2018/10/26 Javascript
vue 实现click同时传入事件对象和自定义参数
2021/01/29 Vue.js
[14:50]2018DOTA2亚洲邀请赛开幕式
2018/04/03 DOTA
python中类和实例如何绑定属性与方法示例详解
2017/08/18 Python
django2 快速安装指南分享
2018/01/05 Python
Python查找两个有序列表中位数的方法【基于归并算法】
2018/04/20 Python
使用Python监控文件内容变化代码实例
2018/06/04 Python
使用python 写一个静态服务(实战)
2019/06/28 Python
python实现交并比IOU教程
2020/04/16 Python
探亲邀请信范文
2014/01/30 职场文书
义和团口号
2014/06/17 职场文书
安全生产月标语
2014/10/07 职场文书
学校党委干部个人对照检查材料思想汇报
2014/10/09 职场文书
小学五一劳动节活动总结
2015/02/09 职场文书
《认识钟表》教学反思
2016/02/16 职场文书
导游词之重庆钓鱼城
2019/09/19 职场文书
Mysql 设置boolean类型的操作
2021/06/04 MySQL
Python re.sub 反向引用的实现
2021/07/07 Python