实例讲解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命令行参数解析模块optparse使用实例
Apr 13 Python
python搜索指定目录的方法
Apr 29 Python
以一个投票程序的实例来讲解Python的Django框架使用
Feb 18 Python
python复制文件到指定目录的实例
Apr 27 Python
python使用pdfminer解析pdf文件的方法示例
Dec 20 Python
Python3.5文件修改操作实例分析
May 01 Python
把django中admin后台界面的英文修改为中文显示的方法
Jul 26 Python
Python如何实现的二分查找算法
May 27 Python
使用OpenCV获取图像某点的颜色值,并设置某点的颜色
Jun 02 Python
python3.7添加dlib模块的方法
Jul 01 Python
Python第三方包PrettyTable安装及用法解析
Jul 08 Python
python批量修改交换机密码的示例
Sep 22 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中try catch捕获异常实例详解
2020/08/06 PHP
splice slice区别
2006/10/09 Javascript
封装好的省市地区联动控件附下载
2007/08/13 Javascript
一个关于jqGrid使用的小例子(行按钮)
2011/11/04 Javascript
查看源码的工具 学习jQuery源码不错的工具
2011/12/26 Javascript
jQuery 拖动层(在可视区域范围内)
2012/05/24 Javascript
千分位数字格式化(用逗号隔开 代码已做了修改 支持0-9位逗号隔开)的JS代码
2013/12/05 Javascript
javascript 获取iframe里页面中元素值的方法
2014/02/17 Javascript
jquery自动将form表单封装成json的具体实现
2014/03/17 Javascript
js行号显示的文本框实现效果(兼容多种浏览器 )
2015/10/23 Javascript
js获取鼠标位置实例详解
2015/12/09 Javascript
原生js实现图片轮播特效
2015/12/18 Javascript
js插件Jcrop自定义截取图片功能
2016/10/14 Javascript
vue2利用Bus.js如何实现非父子组件通信详解
2017/08/25 Javascript
JavaScript模块详解
2017/12/18 Javascript
js中url对象化管理分析
2017/12/29 Javascript
JavaScript实现简单的文本逐字打印效果示例
2018/04/12 Javascript
[05:15]DOTA2英雄梦之声_第16期_灰烬之灵
2014/06/21 DOTA
[06:20]2015国际邀请赛第三日top10
2015/08/08 DOTA
Python采用raw_input读取输入值的方法
2014/08/18 Python
Python 数据结构之堆栈实例代码
2017/01/22 Python
Selenium定位元素操作示例
2018/08/10 Python
python生成九宫格图片
2018/11/19 Python
Matplotlib中rcParams使用方法
2021/01/05 Python
Python开发.exe小工具的详细步骤
2021/01/27 Python
CSS3 画基本图形,圆形、椭圆形、三角形等
2016/09/20 HTML / CSS
英国最大的宠物食品和宠物用品网上零售商: Zooplus
2016/08/01 全球购物
JBL澳大利亚官方商店:扬声器、耳机和音响系统
2018/05/24 全球购物
美国正宗设计师眼镜在线零售商:EYEZZ
2019/03/23 全球购物
SQL Server笔试题
2012/01/10 面试题
求职自荐信格式
2013/12/04 职场文书
《植物妈妈有办法》教学反思
2014/02/25 职场文书
员工培训协议书
2014/09/15 职场文书
先进单位申报材料
2014/12/25 职场文书
药品销售内勤岗位职责
2015/04/13 职场文书
2015年个人实习工作总结
2015/05/28 职场文书