实例讲解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 14 Python
Python中利用sorted()函数排序的简单教程
Apr 27 Python
Python实现文件按照日期命名的方法
Jul 09 Python
Python内置函数OCT详解
Nov 09 Python
python僵尸进程产生的原因
Jul 21 Python
NumPy 数组使用大全
Apr 25 Python
使用Python画股票的K线图的方法步骤
Jun 28 Python
Python函数式编程指南:对生成器全面讲解
Nov 19 Python
解决tensorflow打印tensor有省略号的问题
Feb 04 Python
Python填充任意颜色,不同算法时间差异分析说明
May 16 Python
Pytorch实现将模型的所有参数的梯度清0
Jun 24 Python
详解Python中list[::-1]的几种用法
Nov 16 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
linux系统上支持php的 iconv()函数的方法
2011/10/01 PHP
php自定义session示例分享
2014/04/22 PHP
PHP中使用asort进行中文排序失效的问题处理
2014/08/18 PHP
php使用$_POST或$_SESSION[]向js函数传参
2014/09/16 PHP
ThinkPHP Where 条件中常用表达式示例(详解)
2017/03/31 PHP
PHP基于递归实现的约瑟夫环算法示例
2017/08/27 PHP
一个关于javascript匿名函数的问题分析
2012/03/30 Javascript
JS获取url链接字符串 location.href
2013/12/23 Javascript
jquery插件开发之实现google+圈子选择功能
2014/03/10 Javascript
JQuery自动触发事件的方法
2015/06/13 Javascript
js实现类似MSN提示的页面效果代码分享
2015/08/24 Javascript
Jquery使用小技巧汇总
2015/12/29 Javascript
详解Javascript模板引擎mustache.js
2016/01/20 Javascript
js严格模式总结(分享)
2016/08/22 Javascript
微信小程序 扎金花简单实例
2017/02/21 Javascript
微信小程序webview组件交互,内联h5页面并网页实现微信支付实现解析
2019/08/16 Javascript
vue中使用v-model完成组件间的通信
2019/08/22 Javascript
小程序实现上下移动切换位置
2019/09/23 Javascript
Ant Design moment对象和字符串之间的相互转化教程
2020/10/27 Javascript
跟老齐学Python之有容乃大的list(2)
2014/09/15 Python
Python实现复杂对象转JSON的方法示例
2017/06/22 Python
Python使用re模块正则提取字符串中括号内的内容示例
2018/06/01 Python
DJango的创建和使用详解(默认数据库sqlite3)
2019/11/18 Python
pytorch快速搭建神经网络_Sequential操作
2020/06/17 Python
python报错TypeError: ‘NoneType‘ object is not subscriptable的解决方法
2020/11/05 Python
详解Canvas事件绑定
2018/06/27 HTML / CSS
html5中地理位置定位api接口开发应用小结
2013/01/04 HTML / CSS
CHARLES & KEITH澳大利亚官网:新加坡时尚品牌
2019/01/22 全球购物
数据库什么时候应该被重组
2012/11/02 面试题
餐饮业经理竞聘演讲稿
2014/01/14 职场文书
法制宣传标语集锦
2014/06/25 职场文书
大学团日活动新闻稿
2014/09/10 职场文书
2014年学校卫生工作总结
2014/11/20 职场文书
2016年基层党支部书记公开承诺书
2016/03/25 职场文书
公司开业的祝贺语大全(60条)
2019/07/05 职场文书
详解Vue router路由
2021/11/20 Vue.js