实例讲解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执行外部程序的常用方法小结
Mar 21 Python
python基于socket实现网络广播的方法
Apr 29 Python
详解Django中Request对象的相关用法
Jul 17 Python
常见python正则用法的简单实例
Jun 21 Python
Python使用random.shuffle()打乱列表顺序的方法
Nov 08 Python
Python 分享10个PyCharm技巧
Jul 13 Python
python getpass实现密文实例详解
Sep 24 Python
Python创建空列表的字典2种方法详解
Feb 13 Python
Python基于xlrd模块处理合并单元格
Jul 28 Python
快速创建python 虚拟环境
Nov 28 Python
python 批量压缩图片的脚本
Jun 02 Python
Python可视化学习之seaborn调色盘
Feb 24 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
虹吸壶是谁发明的?煮出来的咖啡好喝吗
2021/03/04 冲泡冲煮
php中批量修改文件后缀名的函数代码
2011/10/23 PHP
PHP处理excel cvs表格的方法实例介绍
2013/05/13 PHP
用Json实现PHP与JavaScript间数据交换的方法详解
2013/06/20 PHP
php实现的支持imagemagick及gd库两种处理的缩略图生成类
2014/09/23 PHP
PHP扩展开发入门教程
2015/02/26 PHP
PHP排序算法之冒泡排序(Bubble Sort)实现方法详解
2018/04/20 PHP
Js如何判断客户端是PC还是手持设备简单分析
2012/11/22 Javascript
JavaScript生成GUID的多种算法小结
2013/08/18 Javascript
jquery中prop()方法和attr()方法的区别浅析
2013/09/06 Javascript
js实现div的切换特效上一个下一个
2014/02/11 Javascript
jQuery实现购物车计算价格功能的方法
2015/03/25 Javascript
jQuery中dom元素上绑定的事件详解
2015/04/24 Javascript
一个用jquery写的判断div滚动条到底部的方法【推荐】
2016/04/29 Javascript
JS版微信6.0分享接口用法分析
2016/10/13 Javascript
jQuery初级教程之网站品牌列表效果
2017/08/02 jQuery
微信小程序事件 bindtap bindinput代码实例
2019/08/26 Javascript
[00:33]DOTA2上海特级锦标赛 CDEC战队宣传片
2016/03/04 DOTA
[55:42]VG vs VGJ.T 2018国际邀请赛淘汰赛BO1 8.21
2018/08/22 DOTA
[01:54]TI珍贵瞬间系列(三):翻盘
2020/08/28 DOTA
python中根据字符串调用函数的实现方法
2016/06/12 Python
python基于物品协同过滤算法实现代码
2018/05/31 Python
Python 中包/模块的 `import` 操作代码
2019/04/22 Python
Python实现Linux监控的方法
2019/05/16 Python
使用apiDoc实现python接口文档编写
2019/11/19 Python
python中的selenium安装的步骤(浏览器自动化测试框架)
2020/03/17 Python
利用Opencv实现图片的油画特效实例
2021/02/28 Python
css3加js做一个简单的3D行星运转效果实例代码
2017/01/18 HTML / CSS
新加坡第一大健康与美容零售商:屈臣氏新加坡(Watsons Singapore)
2020/12/11 全球购物
求职自荐信
2013/12/14 职场文书
实习评语大全
2014/04/26 职场文书
主题教育活动总结
2014/05/05 职场文书
产品包装策划方案
2014/05/18 职场文书
学校火灾防控方案
2014/06/09 职场文书
感恩祖国演讲稿
2014/09/09 职场文书
2016年大学光棍节活动总结
2016/04/05 职场文书