实例讲解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实现的二叉树算法和kmp算法实例
Apr 25 Python
Python2.6版本中实现字典推导 PEP 274(Dict Comprehensions)
Apr 28 Python
使用paramiko远程执行命令、下发文件的实例
Oct 01 Python
Python数据分析之获取双色球历史信息的方法示例
Feb 03 Python
Python OpenCV处理图像之图像直方图和反向投影
Jul 10 Python
pygame游戏之旅 如何制作游戏障碍
Nov 20 Python
Python3.5内置模块之os模块、sys模块、shutil模块用法实例分析
Apr 27 Python
Django利用cookie保存用户登录信息的简单实现方法
May 27 Python
Pytest框架之fixture的详细使用教程
Apr 07 Python
python os模块在系统管理中的应用
Jun 22 Python
python实现ROA算子边缘检测算法
Apr 05 Python
Python实现滑雪小游戏
Sep 25 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教程 插件机制在PHP中实现方案
2012/11/02 PHP
使用PHP下载CSS文件中的图片的代码
2013/09/24 PHP
php生成随机颜色方法汇总
2014/12/03 PHP
PHP中使用socket方式GET、POST数据实例
2015/04/02 PHP
JavaScript 参数中的数组展开 [译]
2012/09/21 Javascript
JavaScript中的值类型转换介绍
2014/12/31 Javascript
教你如何终止JQUERY的$.AJAX请求
2016/02/23 Javascript
基于JavaScript实现快速转换文本语言(繁体中文和简体中文)
2016/03/07 Javascript
ES6生成器用法实例分析
2017/04/10 Javascript
NodeJS收发GET和POST请求的示例代码
2017/08/25 NodeJs
vue系列之动态路由详解【原创】
2017/09/10 Javascript
JavaScript:ES2019 的新特性(译)
2019/08/08 Javascript
基于layui table返回的值的多级嵌套的解决方法
2019/09/19 Javascript
小程序表单认证布局及验证详解
2020/06/19 Javascript
Vue页面手动刷新,实现导航栏激活项还原到初始状态
2020/08/06 Javascript
js代码编写无缝轮播图
2020/09/13 Javascript
[01:13:17]Secret vs NB 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Python 文件和输入输出小结
2013/10/09 Python
Python实现学校管理系统
2018/01/11 Python
python 实现turtle画图并导出图片格式的文件
2019/12/07 Python
使用 Python 在京东上抢口罩的思路详解
2020/02/27 Python
Python面向对象程序设计之静态方法、类方法、属性方法原理与用法分析
2020/03/23 Python
python 贪心算法的实现
2020/09/18 Python
Python数据可视化常用4大绘图库原理详解
2020/10/23 Python
Python实现FTP文件定时自动下载的步骤
2020/12/19 Python
详解CSS的border边框属性及其在CSS3中的新特性
2016/05/10 HTML / CSS
图解CSS3制作圆环形进度条的实例教程
2016/05/26 HTML / CSS
电脑租赁公司创业计划书
2014/01/08 职场文书
电子商务个人职业生涯规划范文
2014/02/12 职场文书
教师师德承诺书
2014/03/26 职场文书
求职意向书
2014/04/01 职场文书
员工安全生产责任书
2014/07/22 职场文书
总经理2015中秋节致辞
2015/07/29 职场文书
小学一年级语文教学反思
2016/03/03 职场文书
Golang 入门 之url 包
2022/05/04 Golang
Python实现聚类K-means算法详解
2022/07/15 Python