实例讲解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通过BF算法实现关键词匹配的方法
Mar 13 Python
Python操作Access数据库基本步骤分析
Sep 19 Python
PyQt5每天必学之弹出消息框
Apr 19 Python
Python中循环引用(import)失败的解决方法
Apr 22 Python
Python写一个基于MD5的文件监听程序
Mar 11 Python
Pandas中DataFrame的分组/分割/合并的实现
Jul 16 Python
Python使用百度api做人脸对比的方法
Aug 28 Python
基于python监控程序是否关闭
Jan 14 Python
浅谈keras中自定义二分类任务评价指标metrics的方法以及代码
Jun 11 Python
用python读取xlsx文件
Dec 17 Python
用Python创建简易网站图文教程
Jun 11 Python
Python可视化神器pyecharts绘制地理图表
Jul 07 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和XSL stylesheets转换XML文档
2006/10/09 PHP
phpinfo 系统查看参数函数代码
2009/06/05 PHP
PHP使用Mysql事务实例解析
2014/09/08 PHP
php使用for语句输出三角形的方法
2015/06/09 PHP
php实现源代码加密的方法
2015/07/11 PHP
深入浅析php中sprintf与printf函数的用法及区别
2016/01/08 PHP
PHP获取用户访问IP地址的5种方法
2016/05/16 PHP
同一页面多个商品倒计时JS 基于面向对象的javascript
2012/02/16 Javascript
Javascript图像处理思路及实现代码
2012/12/25 Javascript
原生javascript图片自动或手动切换示例附演示源码
2013/09/04 Javascript
node.js中RPC(远程过程调用)的实现原理介绍
2014/12/05 Javascript
node.js中的path.extname方法使用说明
2014/12/09 Javascript
EasyUI中datagrid在ie下reload失败解决方案
2015/03/09 Javascript
JS实现弹出浮动窗口(支持鼠标拖动和关闭)实例详解
2015/08/06 Javascript
Bootstrap的图片轮播示例代码
2015/08/31 Javascript
jQuery实现产品对比功能附源码下载
2016/08/09 Javascript
浅谈js内置对象Math的属性和方法(推荐)
2016/09/19 Javascript
js HTML5上传示例代码完整版
2016/10/10 Javascript
scroll事件实现监控滚动条并分页显示(zepto.js)
2016/12/18 Javascript
用vue封装插件并发布到npm的方法步骤
2017/10/18 Javascript
jQuery实现动态加载select下拉列表项功能示例
2018/05/31 jQuery
详解js获取video任意时间的画面截图
2019/04/17 Javascript
js实现旋转的星空效果
2019/11/01 Javascript
浅析JavaScript 函数防抖和节流
2020/07/13 Javascript
Python httplib模块使用实例
2015/04/11 Python
python实现图像识别功能
2018/01/29 Python
Python基于TCP实现会聊天的小机器人功能示例
2018/04/09 Python
pip install python 快速安装模块的教程图解
2019/10/08 Python
联想哥伦比亚网上商城:Lenovo Colombia
2017/01/10 全球购物
加拿大在线隐形眼镜和眼镜店:VisionPros
2019/10/06 全球购物
你经历的项目中的SCM配置项主要有哪些?什么是配置项?
2013/11/04 面试题
工商干部先进事迹
2014/05/14 职场文书
销售求职信范文
2014/05/26 职场文书
安全生产工作汇报材料
2014/10/28 职场文书
部门经理迟到检讨书
2015/02/16 职场文书
小学教师暑期培训心得体会
2016/01/09 职场文书