实例讲解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之禅》中对于Python编程过程中的一些建议
Apr 03 Python
5种Python单例模式的实现方式
Jan 14 Python
python常用知识梳理(必看篇)
Mar 23 Python
理论讲解python多进程并发编程
Feb 09 Python
Python获取指定字符前面的所有字符方法
May 02 Python
Python字典创建 遍历 添加等实用基础操作技巧
Sep 13 Python
python numpy 常用随机数的产生方法的实现
Aug 21 Python
Python实现的微信红包提醒功能示例
Aug 22 Python
Python 解决OPEN读文件报错 ,路径以及r的问题
Dec 19 Python
tensorflow模型继续训练 fineturn实例
Jan 21 Python
Jupyter notebook如何实现指定浏览器打开
May 13 Python
如何使用python自带IDLE的几种方法
Oct 10 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中用hash实现的数组
2011/07/17 PHP
PHP中构造函数和析构函数解析
2014/10/10 PHP
Laravel 连接(Join)示例
2019/10/16 PHP
PHP const定义常量及global定义全局常量实例解析
2020/05/28 PHP
JavaScript基本概念初级讲解论坛贴的学习记录
2009/02/22 Javascript
jquery tools之tabs 选项卡/页签
2009/07/25 Javascript
解决3.01版的jquery.form.js中文乱码问题的解决方法
2012/03/08 Javascript
js的回调函数详解
2015/01/05 Javascript
JavaScript类型检测之typeof 和 instanceof 的缺陷与优化
2016/01/13 Javascript
浅析JavaScript Array和string的转换(推荐)
2016/05/20 Javascript
jQuery、zepto、js常用小技巧
2017/02/12 Javascript
js中作用域的实例解析
2017/03/16 Javascript
bootstrap table表格使用方法详解
2017/04/26 Javascript
jquery实现放大镜简洁代码(推荐)
2017/06/08 jQuery
Vue多种方法实现表头和首列固定的示例代码
2018/02/02 Javascript
在Create React App中使用CSS Modules的方法示例
2019/01/15 Javascript
vue防止花括号{{}}闪烁v-text和v-html、v-cloak用法示例
2019/03/13 Javascript
javascript原型链学习记录之继承实现方式分析
2019/05/01 Javascript
[48:44]2014 DOTA2国际邀请赛中国区预选赛5.21 TongFu VS HGT
2014/05/22 DOTA
[00:52]玛尔斯技能全介绍
2019/03/06 DOTA
python检测主机的连通性并记录到文件的实例
2018/06/21 Python
keras load model时出现Missing Layer错误的解决方式
2020/06/11 Python
鱼油专家:Omegavia
2016/10/10 全球购物
美国网上鞋城:Shoeline.com
2016/11/17 全球购物
美体小铺英国官网:The Body Shop英国
2017/01/24 全球购物
全球最大的房车租赁市场:Outdoorsy
2018/09/19 全球购物
为什么如下的代码int a=100,b=100;long int c=a * b;不能工作
2013/11/29 面试题
软件设计的目标是什么
2016/12/04 面试题
历史学专业推荐信
2013/11/06 职场文书
关爱留守儿童倡议书
2014/04/15 职场文书
文明市民先进事迹
2014/05/15 职场文书
《中国梦我的梦》大学生演讲稿
2014/08/20 职场文书
开展党的群众路线教育实践活动领导班子对照检查材料
2014/09/25 职场文书
交通肇事罪辩护词
2015/05/21 职场文书
cf战队宣传语
2015/07/13 职场文书
python 如何用terminal输入参数
2021/05/25 Python