实例讲解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中的闭包总结
Sep 18 Python
python开发之基于thread线程搜索本地文件的方法
Nov 11 Python
Python找出list中最常出现元素的方法
Jun 14 Python
Python利用QQ邮箱发送邮件的实现方法(分享)
Jun 09 Python
python实现BackPropagation算法
Dec 14 Python
python matlibplot绘制3D图形
Jul 02 Python
pandas.DataFrame的pivot()和unstack()实现行转列
Jul 06 Python
Python selenium的基本使用方法分析
Dec 21 Python
pytorch中的上采样以及各种反操作,求逆操作详解
Jan 03 Python
Python求两个字符串最长公共子序列代码实例
Mar 05 Python
Pyinstaller打包Scrapy项目的实现步骤
Sep 22 Python
python中Matplotlib绘制直线的实例代码
Jul 04 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
adodb与adodb_lite之比较
2006/12/31 PHP
解决File size limit exceeded 错误的方法
2013/06/14 PHP
PHP图片处理之图片旋转和图片翻转实例
2014/11/19 PHP
php解决和避免form表单重复提交的几种方法
2016/08/31 PHP
laravel中Redis队列监听中断的分析
2020/09/14 PHP
JavaScript中也使用$美元符号来代替document.getElementById
2010/06/19 Javascript
json的前台操作和后台操作实现代码
2012/01/20 Javascript
Node.js中对通用模块的封装方法
2014/06/06 Javascript
网页禁用右键菜单和鼠标拖动选择方法小结
2015/02/25 Javascript
js中this用法实例详解
2015/05/05 Javascript
JavaScript中使用自然对数ln的方法
2015/06/14 Javascript
js实现input框文字动态变换显示效果
2015/08/19 Javascript
js+css实现有立体感的按钮式文字竖排菜单效果
2015/09/01 Javascript
AngularJS 单元测试(二)详解
2016/09/21 Javascript
Canvas实现放射线动画效果
2017/02/15 Javascript
JQueryDOM之样式操作
2019/03/27 jQuery
vue 进阶之实现父子组件间的传值
2019/04/26 Javascript
详解Vue 换肤方案验证
2019/08/28 Javascript
为nuxt项目写一个面包屑cli工具实现自动生成页面与面包屑配置
2019/09/29 Javascript
javascript实现蒙版与禁止页面滚动
2020/01/11 Javascript
微信小程序实现modal弹出框遮罩层组件(可带文本框)
2020/12/20 Javascript
python实现停车管理系统
2018/11/30 Python
解决pyttsx3无法封装的问题
2018/12/24 Python
介绍一款python类型检查工具pyright(推荐)
2019/07/03 Python
Opencv python 图片生成视频的方法示例
2020/11/18 Python
HTML5 LocalStorage 本地存储刷新值还在
2017/03/10 HTML / CSS
拥有超过850家商店的美国在线派对商店:Party City
2018/10/21 全球购物
L’Artisan Parfumeur官网:法国香水品牌
2020/08/11 全球购物
医药工作岗位求职信分享
2013/12/31 职场文书
自我鉴定怎么写
2014/01/12 职场文书
党员群众路线对照检查材料思想汇报
2014/09/17 职场文书
因工资原因离职的辞职信范文
2015/05/12 职场文书
《水浒传》读后感3篇(范文)
2019/09/19 职场文书
如何用python反转图片,视频
2021/04/24 Python
十个Python自动化常用操作,即拿即用
2021/05/10 Python
OpenCV-Python实现人脸磨皮算法
2021/06/07 Python