实例讲解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双向链表实现实例代码
Nov 21 Python
Python实现控制台输入密码的方法
May 29 Python
深入理解python try异常处理机制
Jun 01 Python
python中利用xml.dom模块解析xml的方法教程
May 24 Python
Anaconda入门使用总结
Apr 05 Python
用python3教你任意Html主内容提取功能
Nov 05 Python
Django保护敏感信息的方法示例
May 09 Python
在Python中表示一个对象的方法
Jun 25 Python
关于pytorch中网络loss传播和参数更新的理解
Aug 20 Python
python 链接sqlserver 写接口实例
Mar 11 Python
利用Python实现Json序列化库的方法步骤
Sep 09 Python
pycharm 复制代码出现空格的解决方式
Jan 15 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
这部番真是良心,画质好到像风景区,剧情让人跟着小公会热血沸腾
2020/03/10 日漫
最令PHP初学者头痛的十四个问题
2006/07/12 PHP
php动态生成版权所有信息的方法
2015/03/24 PHP
PHP命名空间namespace用法实例分析
2016/09/27 PHP
用JavaScript获取网页中的js、css、Flash等文件
2006/12/20 Javascript
jqgrid 简单学习笔记
2011/05/03 Javascript
浅谈JavaScript Date日期和时间对象
2014/12/29 Javascript
js实现仿网易点击弹出提示同时背景变暗效果
2015/08/13 Javascript
javascript中字体浮动效果的简单实例演示
2015/11/18 Javascript
原生js和jQuery实现淡入淡出轮播效果
2015/12/25 Javascript
动态设置form表单的action属性的值的简单方法
2016/05/25 Javascript
js改变style样式和css样式的简单实例
2016/06/28 Javascript
详细总结Javascript中的焦点管理
2016/09/17 Javascript
jquery插件开发之选项卡制作详解
2017/08/30 jQuery
JS运动特效之完美运动框架实例分析
2018/01/24 Javascript
angular2/ionic2 实现搜索结果中的搜索关键字高亮的示例
2018/08/17 Javascript
微信小程序引用iconfont图标的方法
2018/10/22 Javascript
详解element-ui日期时间选择器的日期格式化问题
2019/04/08 Javascript
js绘制一条直线并旋转45度
2020/08/21 Javascript
详解Python设计模式编程中观察者模式与策略模式的运用
2016/03/02 Python
python中matplotlib实现最小二乘法拟合的过程详解
2017/07/11 Python
python如何为创建大量实例节省内存
2018/03/20 Python
python 获取当天每个准点时间戳的实例
2018/05/22 Python
Python基于OpenCV库Adaboost实现人脸识别功能详解
2018/08/25 Python
python字符串循环左移
2019/03/08 Python
Python生成一个迭代器的实操方法
2019/06/18 Python
python将字母转化为数字实例方法
2019/10/04 Python
利用HTML5中的Canvas绘制一张笑脸的教程
2015/05/07 HTML / CSS
Eclipse面试题
2014/03/22 面试题
大学专科生推荐信范文
2013/11/23 职场文书
大学毕业生通用自我评价
2014/01/05 职场文书
八年级历史教学反思
2014/01/10 职场文书
粗加工管理制度
2014/02/04 职场文书
函授毕业生自我鉴定范文
2014/03/25 职场文书
自荐信大全
2019/03/21 职场文书
世界各国短波电台对东亚播送时间频率表(SW)
2021/06/28 无线电