实例讲解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模块之re正则表达式详解
Feb 03 Python
Python使用asyncio包处理并发详解
Sep 09 Python
python解决js文件utf-8编码乱码问题(推荐)
May 02 Python
实用自动化运维Python脚本分享
Jun 04 Python
python 对dataframe下面的值进行大规模赋值方法
Jun 09 Python
使用 Python 实现简单的 switch/case 语句的方法
Sep 17 Python
python使用numpy读取、保存txt数据的实例
Oct 14 Python
python实现二级登陆菜单及安装过程
Jun 21 Python
django 配置阿里云OSS存储media文件的例子
Aug 20 Python
Flask和pyecharts实现动态数据可视化
Feb 26 Python
基于Python组装jmx并调用JMeter实现压力测试
Nov 03 Python
Python序列化模块JSON与Pickle
Jun 05 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
yii操作cookie实例简介
2014/07/09 PHP
实例讲解PHP面向对象之多态
2014/08/20 PHP
php分割合并两个字符串的函数实例
2015/06/19 PHP
Laravel 5.5 的自定义验证对象/类示例代码详解
2017/08/29 PHP
JQuery魔力之$(&quot;tagName&quot;)与selector
2012/03/05 Javascript
浅析js中2个等号与3个等号的区别
2013/08/06 Javascript
javascript中数组的冒泡排序使用示例
2013/12/18 Javascript
js实现飞入星星特效代码
2014/10/17 Javascript
Nodejs中读取中文文件编码问题、发送邮件和定时任务实例
2015/01/01 NodeJs
JavaScript原生对象之Number对象的属性和方法详解
2015/03/13 Javascript
jQuery easyui的validatebox校验规则扩展及easyui校验框validatebox用法
2016/01/18 Javascript
AngularJS中比较两个数组是否相同
2016/08/24 Javascript
Reactjs实现通用分页组件的实例代码
2017/01/19 Javascript
讲解vue-router之什么是动态路由
2018/05/28 Javascript
[50:28]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 Newbee vs KG
2018/04/01 DOTA
python基础教程之python消息摘要算法使用示例
2014/02/10 Python
python开发之thread实现布朗运动的方法
2015/11/11 Python
python 根据正则表达式提取指定的内容实例详解
2016/12/04 Python
Python实现的圆形绘制(画圆)示例
2018/01/31 Python
Linux下安装python3.6和第三方库的教程详解
2018/11/09 Python
PowerBI和Python关于数据分析的对比
2019/07/11 Python
浅谈spring boot 集成 log4j 解决与logback冲突的问题
2020/02/20 Python
Python检测端口IP字符串是否合法
2020/06/05 Python
浅谈pytorch中torch.max和F.softmax函数的维度解释
2020/06/28 Python
python中count函数知识点浅析
2020/12/17 Python
Python Selenium库的基本使用教程
2021/01/04 Python
css3 transform及原生js实现鼠标拖动3D立方体旋转
2016/06/20 HTML / CSS
加拿大消费电子和手机购物网站:The Source
2017/01/28 全球购物
什么是.net
2015/08/03 面试题
党建示范点实施方案
2014/03/12 职场文书
法院信息化建设方案
2014/05/21 职场文书
出生医学证明书
2014/09/15 职场文书
春季运动会开幕词
2015/01/28 职场文书
2015秋季开学演讲稿范文
2015/07/16 职场文书
python中字符串String及其常见操作指南(方法、函数)
2022/04/06 Python
HttpClient实现表单提交上传文件
2022/08/14 Java/Android