实例讲解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 相关文章推荐
在Ubuntu系统下安装使用Python的GUI工具wxPython
Feb 18 Python
Python 迭代器与生成器实例详解
May 18 Python
Python基于递归算法实现的走迷宫问题
Aug 04 Python
python安装Scrapy图文教程
Aug 14 Python
Pytorch evaluation每次运行结果不同的解决
Jan 02 Python
mac使用python识别图形验证码功能
Jan 10 Python
Django高并发负载均衡实现原理详解
Apr 04 Python
Django debug为True时,css加载失败的解决方案
Apr 24 Python
Python中常见的导入方式总结
May 06 Python
python3中apply函数和lambda函数的使用详解
Feb 28 Python
Python的property属性详细讲解
Apr 11 Python
Python代码实现双链表
May 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中实现简单的ACL 完结篇
2011/09/07 PHP
PHP易混淆函数的区别及用法汇总
2014/11/22 PHP
PHP Cookei记录用户历史浏览信息的代码
2016/02/03 PHP
PHP实现根据时间戳获取周几的方法
2016/02/26 PHP
WordPress分页伪静态加html后缀
2016/06/08 PHP
JQuery实现鼠标滑过显示导航下拉列表
2013/09/12 Javascript
javascript得到当前页的来路即前一页地址的方法
2014/02/18 Javascript
学习javascript的闭包,原型,和匿名函数之旅
2015/10/18 Javascript
在web中js实现类似excel的表格控件
2016/09/01 Javascript
jQuery的Cookie封装,与PHP交互的简单实现
2016/10/05 Javascript
Angular.js中用ng-repeat-start实现自定义显示
2016/10/18 Javascript
css配合JavaScript实现tab标签切换效果
2018/10/11 Javascript
使用Vue做一个简单的todo应用的三种方式的示例代码
2018/10/20 Javascript
vue-cli3+typescript初体验小结
2019/02/28 Javascript
Vue组件通信的几种实现方法
2019/04/25 Javascript
基于node+vue实现简单的WebSocket聊天功能
2020/02/01 Javascript
[04:49]期待西雅图之战 2016国际邀请赛中国区预选赛WINGS战队赛后采访
2016/06/29 DOTA
Python数据结构之翻转链表
2017/02/25 Python
python利用rsa库做公钥解密的方法教程
2017/12/10 Python
Python实现生成随机数据插入mysql数据库的方法
2017/12/25 Python
python实现控制台打印的方法
2019/01/12 Python
python selenium 弹出框处理的实现
2019/02/26 Python
python使用thrift教程的方法示例
2019/03/21 Python
python解压TAR文件至指定文件夹的实例
2019/06/10 Python
Pytorch中的VGG实现修改最后一层FC
2020/01/15 Python
Django model重写save方法及update踩坑详解
2020/07/27 Python
Matplotlib 折线图plot()所有用法详解
2020/07/28 Python
python3.9.1环境安装的方法(图文)
2021/02/02 Python
加拿大快时尚零售商:Ardene
2018/02/14 全球购物
德国自行车商店:Tretwerk
2019/06/21 全球购物
Marc O’Polo俄罗斯官方在线商店:德国高端时尚品牌
2019/12/26 全球购物
澳大利亚电商Catch新西兰站:Catch.co.nz
2020/05/30 全球购物
经典大学生求职信范文
2014/01/06 职场文书
旷课检讨书范文
2015/01/27 职场文书
感恩母亲节活动总结
2015/02/10 职场文书
Win11安装升级时提示“该电脑必须支持安全启动”
2022/04/19 数码科技