实例讲解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 相关文章推荐
Linux环境下MySQL-python安装过程分享
Feb 02 Python
python实现数据写入excel表格
Mar 25 Python
浅谈dataframe中更改列属性的方法
Jul 10 Python
python 3调用百度OCR API实现剪贴板文字识别
Sep 04 Python
pyshp创建shp点文件的方法
Dec 31 Python
深入解析Python小白学习【操作列表】
Mar 23 Python
python学习——内置函数、数据结构、标准库的技巧(推荐)
Apr 18 Python
关于python字符串方法分类详解
Aug 20 Python
Python模块/包/库安装的六种方法及区别
Feb 24 Python
python利用递归方法实现求集合的幂集
Sep 07 Python
Python根据URL地址下载文件并保存至对应目录的实现
Nov 15 Python
搭建pypi私有仓库实现过程详解
Nov 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编程中八种常见的文件操作方式
2006/11/19 PHP
php与paypal整合方法
2010/11/28 PHP
详解Yii实现分页的两种方法
2017/01/14 PHP
Yii2中多表关联查询hasOne hasMany的方法
2017/02/15 PHP
php微信公众号开发之欢迎老朋友
2018/10/20 PHP
replace()方法查找字符使用示例
2013/10/28 Javascript
在JavaScript中使用timer示例
2014/05/08 Javascript
JavaScript中的6种运算符总结
2014/10/16 Javascript
javascript数组去重方法汇总
2015/04/23 Javascript
javascript中for/in循环及使用技巧
2015/09/01 Javascript
基于JavaScript实现窗口拖动效果
2017/01/18 Javascript
Ionic项目中Native Camera的使用方法
2017/06/07 Javascript
vue2导航根据路由传值,而改变导航内容的实例
2017/11/10 Javascript
NodeJS实现不可逆加密与密码密文保存的方法
2018/03/16 NodeJs
详解Node使用Puppeteer完成一次复杂的爬虫
2018/04/18 Javascript
antd vue table跨行合并单元格,并且自定义内容实例
2020/10/28 Javascript
python统计字符串中指定字符出现次数的方法
2015/04/04 Python
Python字典操作简明总结
2015/04/13 Python
Python排序搜索基本算法之希尔排序实例分析
2017/12/09 Python
使用python和Django完成博客数据库的迁移方法
2018/01/05 Python
Python爬虫工程师面试问题总结
2018/03/22 Python
Python字符串逆序输出的实例讲解
2019/02/16 Python
python爬虫简单的添加代理进行访问的实现代码
2019/04/04 Python
Python中join()函数多种操作代码实例
2020/01/13 Python
tensorflow 固定部分参数训练,只训练部分参数的实例
2020/01/20 Python
Python读写Excel表格的方法
2021/03/02 Python
简洁自适应404页面HTML好看的404源码
2020/12/16 HTML / CSS
耐克美国官网:Nike.com
2016/08/01 全球购物
英国在线药房和在线医生:LloydsPharmacy
2019/10/21 全球购物
将n个数按输入顺序的逆序排列,用函数实现
2012/11/14 面试题
设置器与访问器的定义以及各自特点
2016/01/08 面试题
大学生自我鉴定评语
2014/01/27 职场文书
代理商会议邀请函
2014/01/27 职场文书
2014年国庆标语
2014/06/30 职场文书
党员干部群众路线个人整改措施
2014/09/18 职场文书
Python中的嵌套循环详情
2022/03/23 Python